ReentrantLock源码分析
ReentrantLock源码分析
Author:郑金维
一、ReentrantLock介绍
ReentrantLock,synchronized
synchronized同步锁,锁升级,但是synchronized只有升级,没有降级,锁竞争激烈,不推荐synchronized
ReentrantLock同步锁,内部是基于CAS和AQS实现的,在锁竞争比较激烈时,推荐使用ReentrantLock
ReentrantLock基本使用(可重入锁):
获取锁:ReentrantLock对象.lock();
释放锁:ReentrantLock对象.unlock();
二、CAS、AQS扫盲
2.1 CAS(乐观锁实现方式)
Compare And Swap,是针对一个值进行修改(原子性)
在Java中提供了Unsafe类实现了CAS的操作。
ABA问题:添加版本号。
CAS自旋次数过多:
- synchronized基于自适应自旋锁解决。
- LongAdder基于Cell[],在CAS实现失败,将数据扔到Cell[]中,后期再添加
劣势:CAS只能保证修改一个值的时候,是原子性的。CAS无法直接锁住一段代码。
2.2 AQS
AQS是什么:就是Java中的一个类,名字:AbstractQueuedSynchronizer
AQS是JUC中的一个基础类(并发基础类),很多其他类都是基于AQS实现的:
- ReentrantLock
- CountDownLatch
- 信号量
- 阻塞队列~
- 线程池的Worker类
AQS中维护着一个非常重要的属性:state
还维护着一个双向队列:Node类
三、加锁源码分析
3.1 加锁流程
如果在线程B被唤醒之后:
- 非公平锁,线程B会和其他线程一起竞争锁资源
- 公平锁,其他线程需要先到AQS排队
3.2 lock源码
实现方式就是在ReentrantLock的有参构建中追加,默认false为非公平,true为公平
非公平锁:
final void lock() {
// 先抢一波锁。
if (compareAndSetState(0, 1))
// 如果成功的将state从0改为1,代表拿到锁资源,那就将exclusiveOwnerThread设置为当前线程
setExclusiveOwnerThread(Thread.currentThread());
else
// 没抢到,执行acquire方法
acquire(1);
}
公平锁
final void lock() {
// 执行acquire方法
acquire(1);
}
ReentrantLock竞争锁资源就是以CAS的方式,尝试将state从0改为1,修改成功,获取锁资源成功
3.3 acquire源码
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
3.3.1 tryAcquire方法
非公平锁:
// 非公平锁!
final boolean nonfairTryAcquire(int acquires) {
// 拿到当前线程~
final Thread current = Thread.currentThread();
// 获取state
int c = getState();
// 如果state为0,就再次以CAS的方式尝试拿到锁资源
if (c == 0) {
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
// 当前是锁重入操作!将state+1,即为重入ok,同时释放锁也需要释放多次,释放锁是对state - 1
else if (current == getExclusiveOwnerThread()) {
// state + 1
int nextc = c + acquires;
if (nextc < 0) // overflow 2进制的符号位,加了1,代表是负数。代表超过锁重入线程
throw new Error("Maximum lock count exceeded");
setState(nextc);
// 锁重入成功,返回true
return true;
}
return false;
}
公平锁跟上述代码基本一致,有一个区别,当判断c == 0成功后
- 公平锁需要先尝试排队执行hasQueuedPredecessors()方法,如果需要排队,就排队去
- 如果不需要,那就直接CAS获取锁资源
3.3.2 addWaiter方法
将当前没有获取到锁资源的线程,封装为Node,开始排队
// 将当前没有获取到锁资源的线程,封装为Node,开始排队
private Node addWaiter(Node mode) {
// 将当前线程封装为Node
Node node = new Node(Thread.currentThread(), mode);
// 获取了tail
Node pred = tail;
// 如果tail不为null,说明队列中有节点!
if (pred != null) {
// 当前Node的上一个是之前的tail节点
node.prev = pred;
// 将tail指向当前节点
if (compareAndSetTail(pred, node)) {
// 将pred的next节点,指向当前节点
pred.next = node;
return node;
}
}
// 证明tail指向null,说明队列没Node
enq(node);
// 返回当前节点
return node;
}
private Node enq(final Node node) {
// 死循环
for (;;) {
// 重新获取tail
Node t = tail;
if (t == null) {
// tail还是指向null(第一个来排队的)
// 设置一个空节点作为head和tail
if (compareAndSetHead(new Node()))
tail = head;
} else {
// tail不是null,说明有并发操作,并且有人排到了我的前面
node.prev = t;
// 跟之前操作一样,插入到队列尾巴
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
3.3.3 acquireQueued方法
如果当前节点的上一个节点是head,尝试获取锁资源,如果不是方法会将当前线程进行挂起。
final boolean acquireQueued(final Node node, int arg) {
// 声明变量,
boolean failed = true;
for (;;) {
// 拿到当前节点的prev节点,命名为p
final Node p = node.predecessor();
// p是head头节点,当前节点就再次尝试获取锁资源
if (p == head && tryAcquire(arg)) {
// 当前节点获取锁资源成功,设置当前节点为head,之前head等待GC回收
setHead(node);
p.next = null;
failed = false;
return interrupted;
}
// 查看是否需要挂起当前线程,如果可以挂起
if (shouldParkAfterFailedAcquire(p, node) &&
// 挂起线程,执行了LockSupport.park()挂起线程
parkAndCheckInterrupt())
}
}
private void setHead(Node node) {
head = node;
node.thread = null;
node.prev = null;
}
waitStatus:new的Node默认值为0
-1: 正常
1:凉凉了
-2,-3:不正常,但是可以改~~~
// 判断当前线程的上一个节点的状态是不是-1
// 如果是-1,当前线程可以阻塞
// 如果是1,就往前找,找状态为-1的
// 如果不是-1,也不是1,那就上一个节点状态设置为-1
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
int ws = pred.waitStatus;
if (ws == -)
return true;
if (ws == 1) {
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
四、释放锁源码分析(白给的)
将state - 1,如果减完之后,state是0,unpark唤醒正在排队,并且离head最近的线程
public void unlock() {
// 公平和非公平都走当前方法
sync.release(1);
}
public final boolean release(int arg) {
// 尝试释放
if (tryRelease(arg)) {
// 锁释放成功
// 拿到head
Node h = head;
// 排队的队列中,有head,并且头的状态正常!!!
if (h != null && h.waitStatus != 0)
// 唤醒挂起的线程!
unparkSuccessor(h);
return true;
}
// 释放失败,还得释放几次~~~~
return false;
}
// 针对可重入锁的操作。
protected final boolean tryRelease(int releases) {
// 拿state的值进行-1后的值,赋值给c
int c = getState() - releases;
// 如果释放锁的线程不是拥有锁的线程,抛出异常。
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
// 标识
boolean free = false;
if (c == 0) {
// 如果state-1之后为0,代表释放成功
free = true;
setExclusiveOwnerThread(null);
}
setState(c);
return free;
}
// 唤醒队列中排队的线程
private void unparkSuccessor(Node node) {
int ws = node.waitStatus;
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0);
// 获取head节点的next
Node s = node.next;
// 如果下一个为null,或者下一个节点的状态为取消
if (s == null || s.waitStatus > 0) {
s = null;
// 从后往前,找到节点状态正常的,并且离head最先的节点。(思考问题:为什么从后往前找节点)
for (Node t = tail; t != null && t != node; t = t.prev)
if (t.waitStatus <= 0)
s = t;
}
// 如果离head最近的节点,不为null
if (s != null)
// 唤醒线程!
LockSupport.unpark(s.thread);
}