ReentrantLock加锁流程
流程概述:
- 对于非公平锁而言,首先判断state值是否为0。如果是,竞争锁资源;如果不是,判断持有锁的线程是否是当前线程。如果是,锁重入;如果不是,线程放入AQS链表中
- 对于公平锁而言,首先判断state值是否为0。如果是,还需判断AQS链表中是否有等待的线程,如果有,还需判断head后面的节点是否是当前线程,如果是,才能竞争锁资源。后续的流程与非公平锁一致
- 线程放入AQS链表中。首先判断尾节点是否为null,如果不是,放到尾节点后面,如果为null,创建head节点,将线程放到head节点后面
- 线程放到AQS链表中后,判断当前线程前一个节点是否是head节点,如果是,竞争锁资源,如果不是,判断前一个节点的waitStatus值是否<=0,如果是,将前一个节点的waitStatus值改成-1,并且挂起当前线程。前一个节点的waitStatus值是1,那么会将前一个节点从AQS链表中删除,直到当前节点挂在正常节点的后面
非公平锁
- 生成非公平锁的语句:
ReentrantLock lock = new ReentrantLock(); - lock方法中会执行sync.lock()方法
- 使用CAS操作将state的值从0变成1
- 如果成功,代表加锁成功,会将exclusiveOwnerThread属性值设置成当前线程,代表当前线程获得锁资源
- 如果失败,执行acquire()方法
static final class NonfairSync extends Sync {
private static final long serialVersionUID = 7316153563782823691L;
final void lock() {
// 使用CAS,尝试将state值从0变成1
if (compareAndSetState(0, 1))
// 加锁成功
setExclusiveOwnerThread(Thread.currentThread());
else
// 加锁失败
acquire(1);
}
}
公平锁
- 生成公平锁的语句:
ReentrantLock lock = new ReentrantLock(true); - lock方法中会执行sync.lock()方法
- 执行acquire()方法
static final class FairSync extends Sync {
private static final long serialVersionUID = -3000897897090466540L;
final void lock() {
acquire(1);
}
}
acquire()方法
- tryAcquire()方法是在链表中没有挂起线程的情况下,让当前线程再次尝试获取锁资源,如果成功则返回,否则继续
- addWaiter()方法是将线程封装成node节点后,添加到双向链表的尾部
- acquireQueued()方法会判断此线程是否是链表head伪节点后面的第一个节点,如果是,则再次尝试获取锁资源,如果获取失败,则挂起线程;如果不是,直接挂起
- 最后,将线程的中断标志位设置成true
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
tryAcquire()方法
非公平锁
- 执行nonfairTryAcquire()方法
- 如果state值为0,当前线程尝试获取锁资源,获取成功直接返回,获取失败则继续执行后续代码
- 判断获取锁资源的线程是否是当前线程,如果不是就返回
- 如果是当前线程,state的值继续加1,ReentrantLock支持可重入锁
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
// 尝试获取锁资源
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
// 当前线程拿到锁资源,state+1
int nextc = c + acquires;
// nextc < 0 表示state的值已经达到int类型的最大值,再加1就变成了负数
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
公平锁
- 重写了tryAcquire()方法并且执行
- 如果state值为0,判断双向链表中是否没有等待线程,如果没有,尝试获取锁;如果有,判断第一个等待的线程是否是当前线程,如果是尝试获取锁资源
- 判断获取锁资源的线程是否是当前线程,如果不是就返回
- 如果是当前线程,state的值继续加1,ReentrantLock支持可重入锁
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
// hasQueuedPredecessors()方法是判断双向链表中是否没有等待线程,
// 或者第一个等待的线程是否是当前线程,如果满足则返回false
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
addWaiter()方法
公平锁和非公平锁的addWaiter()方法相同
- 判断双向链表中的尾节点是否为null,如果不为null,将当前节点添加到尾节点后面
- 如果为null,执行enq()方法
- enq()方法里有个死循环,判断尾节点是否为null,如果是,生成一个伪节点
- 然后将当前节点添加到伪节点后面
private Node addWaiter(Node mode) {
Node node = new Node(Thread.currentThread(), mode);
Node pred = tail;
if (pred != null) {
node.prev = pred;
// 将当前节点添加到尾节点后面
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
enq(node);
return node;
}
private Node enq(final Node node) {
for (;;) {
Node t = tail;
if (t == null) { // Must initialize
if (compareAndSetHead(new Node()))
tail = head;
} else {
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
acquireQueued()方法
- acquireQueued()方法里也有个死循环,判断当前节点的前一个节点是否是头节点
- 如果是,尝试获取锁资源,如果成功,设置当前节点为头节点
- 如果不是,执行shouldParkAfterFailedAcquire()方法
final boolean acquireQueued(final Node node, int arg) {
// lock()方法加锁,不用管方法中的线程中断逻辑
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
final Node p = node.predecessor();
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
// shouldParkAfterFailedAcquire()方法修改当前节点和前面节点的waitStatus值
// parkAndCheckInterrupt()方法是将当前线程挂起
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
shouldParkAfterFailedAcquire()方法
- 如果当前节点的前一个节点waitStatus值是-1,返回true
- 如果当前节点的前一个节点waitStatus值>0,前一个节点从双向链表中删除, 直到前一个节点waitStatus值<=0,将当前节点放到这个节点后面,然后返回false
- 如果当前节点的前一个节点waitStatus值是0,则将waitStatus设置成-1,然后返回false
- shouldParkAfterFailedAcquire方法返回false后,按照acquireQueued方法逻辑,线程会再次判断前一个节点是否是head节点,如果是则竞争锁资源。这个逻辑保证了unlock方法唤醒的有效节点可以正常竞争锁资源
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
int ws = pred.waitStatus;
if (ws == Node.SIGNAL)
return true;
if (ws > 0) {
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
parkAndCheckInterrupt()方法
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);
return Thread.interrupted();
}
lock方法加锁流程图
lock方法加锁逻辑小结
- 在锁被释放的情况下,对于公平锁来说,执行tryAcquire方法加锁时,首先判断队列中是否有等待的节点,如果有,判断head节点后面的线程是否是当前线程,如果是,则获取锁,如果不是,则不去尝试获取锁,直接返回false;如果队列为空,尝试获取锁。对于非公平锁来说,无论队列中是否有等待的节点,都会尝试获取锁。tryAcquire方法返回true代表获取锁成功,返回false代表获取锁失败。
- 获取锁失败的话,会执行addWaiter方法。此方法无论是公平锁还是非公平锁,逻辑是一样的。将当前线程封装成Node节点,添加到队列的尾部。如果队列为空,首先创建head节点,再将当前线程节点添加到head节点后面。为了确保添加尾部节点时线程安全,使用了CAS机制。如果第一次添加失败,会执行enq方法。这个方法逻辑是个死循环,目的就是添加尾部节点成功。
- 当添加尾部节点成功后,执行acquireQueued方法。此方法中会判断当前线程的节点的前一个节点是否是head节点,如果是,当前线程尝试获取锁,获取成功返回true;如果不是,判断前一个节点的waitStatus值是否是1,如果是,表示这个节点是要被移除的节点,那么就将节点移除后,再判断前一个节点的waitStatus值,直到遇到一个节点的waitStatus值小于等于0,才将当前节点设置到此节点后面,并且修改此节点的waitStatus值为-1,当前节点的改成0。