前言
之前写过关于AQS的文章,也讲了关于 ReetrantLock
的源码分析。这里主要是想结合源码分析下Condition
的 await 和 signal ,写ReentrantLock那么还是得提下 AQS 的。
一、关于AQS 中几个重要的属性
- AQS 中维护了一个很重要的变量
state
, 它是int型的,表示加锁的状态,初始状态值为0; - 另外 AQS 还维护了一个很重要的变量
exclusiveOwnerThread
,它表示的是获得锁的线程,也叫独占线程,ReentrantLock 中的可重入就是用到该属性,当state != 0
且exclusiveOwnerThread 等于当前线程
,那么表示的是当前线程来获取锁了,是可以直接获取锁成功,即锁的可重入,这也表示ReentrantLock
是可重入锁。 AQS
中还有一个用来存储获取锁失败线程的队列,它是一个FIFO的双链表结构,以及head
和tail
结点。- 双链表的结点 Node 包含的主要属性如下:
Node
: SHARED 和 EXCLUSIVE : 用于标识该结点是共享模式还是独占模式
int : waitStatus
: 标识该结点的等待状态,有如下几种值:
CANCELLED:值为1,表示该结点被取消了,比如使用 tryLock(1000, TimeUnit.MILLISECONDS) 获取锁超时就会被取消
SIGNAL: 值为-1,表示此结点的后继结点的线程已经通过LockSupport.park()挂起了,需要前面的结点在释放锁或者取消的时候来唤醒该线程
CONDITION: 值为-2,表示该结点在condition队列中,一般是通过 condition.await() 操作,然后该结点插入 condition 的尾部
PROPAGATE: 值为-3,共享模式的头结点可能处于此状态,表示往下传播。假设有2个线程同时释放锁,通过竞争,其中一条负责唤醒后继结点,而另一条则将头结点设置为此状态,新结点唤醒后,直接根据头结点的此状态来唤醒 下下个结点。
注意,当 new Node()
的时候 waitStatus 默认等于0。
二、ReetrantLock 的结构
- ReetrantLock 包含公平锁和非公平锁,默认的是非公平锁。
- ReetrantLock 包含一个抽象的内部类: Sync,Sync 继承了 AQS,如下:
abstract static class Sync extends AbstractQueuedSynchronizer
- ReetrantLock 中的 公平和非公平锁分别继承自 Sync
三、ReetrantLock 的获取和释放锁的分析
这里就以默认的非公平锁的源码来分析,版本为 jdk1.8
。
ReentrantLock lock = new ReentrantLock()
try {
lock.lock()
...
} finally {
lock.unLock()
}
和 synchronized
不一样,ReentrantLock
是不会主动释放锁的,所以需要在 finally
块中主动调用 unLock
释放。
这里假设有A,B,C,D 4个线程同时调用 lock 获取锁,且每个线程获取锁之后执行的任务都非常耗时。
1. lock.lock()
- 会调用
ReentrantLock
的 lock 方法,如下:
public void lock() {
sync.lock();
}
由于默认的是非公平锁,所以 调用的是 NonfairSync
的 lock 方法,如下:
final void lock() {
// state 的默认值是0,所以第一个线程来获取锁的时候 cas 操作返回true
// 线程释放锁的时候会把 state 再次设置为 0
if (compareAndSetState(0, 1)) // 注释【1】
// 把获取锁的线程设置为独占线程
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1); // 注释【2】
}
由于线程 A
是第一个请求锁的,所以通过 CAS 把 AQS 的 state
值设置为1,表示 线程A已经获取了锁,且设置线程A 为独占线程。
此时,假设线程B
再来获取锁的时候 走到注释【1】
,此时state的值为1,所以cas 操作会失败,接着走到 注释【2】
,acquire
是 AQS 里面的方法,如下:
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
上面的方法包含了3个重要的步骤:
tryAcquire
: 再次尝试去获取锁,该方法在是在 ReentrantLock 的内部类NonfairSync
中。addWaiter
:把当前线程转化为Node结点,且插入等待队列的末尾,如果是第一个插入等待队列的结点,还需要 new 一个空的结点作为head
结点。acquireQueued
:这个方法 判断 node 结点的前驱结点是否是head
结点,如果是,那么再次尝试去获取锁,如果获取锁成功,那么设置当前的 node 为head
结点;如果获取锁失败,那么会阻塞(挂起)当前线程。
tryAcquire
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
// 表示之前获取锁的线程已经释放了锁
if (c == 0) {
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
// 表示之前获取锁的线程未释放锁,然后判断当前线程是否就是之前获取锁的线程
// 如果是,那么state + 1,这里就是锁的重入了
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
tryAcquire
获取锁成功则返回 true,否则返回 false 。下面看看addWaiter(Node.EXCLUSIVE)
, Node.EXCLUSIVE 前面也说过,指的是独占锁,代码如下:
private Node addWaiter(Node mode) {
// new Node ,Node 的thread 赋值为当前线程
Node node = new Node(Thread.currentThread(), mode);
// Try the fast path of enq; backup to full enq on failure
Node pred = tail;
// 第一个获取锁失败的线程走到这里时,head 和 tail 都等于null
if (pred != null) {
// 不等于null,说明等待队列里面有等待的线程了,
// 下面的操作就是把 node 插入到队列的而尾部然后返回 node
node.prev = pred;
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
// 走到这里,说明等待队列为空
enq(node);
return node;
}
由于 线程B
是第一个未获取到锁的线程,此时 head
和 tail
都是 null,所以走到 enq(node)
:
private Node enq(final Node node) {
for (;;) {
Node t = tail; //注释【1】
if (t == null) { // 注释【2】
if (compareAndSetHead(new Node())) //注释【3】
tail = head; //注释【4】
} else {
node.prev = t; //注释【5】
if (compareAndSetTail(t, node)) { //注释【6】
t.next = node; //注释【7】
return t; //注释【8】
}
}
}
}
这里画图来展示吧:
注释【1】:由于此时 tail == null ,所以注释【2】成立;
注释【3】:new 一个空的结点,然后通过 cas 操作把这个空结点设置为 head,此时,head 结点的 waitStatus 值为0(默认值)
注释【4】:让 tail 指向 head,执行完 的效果如下:
此时 head 和 tail 都指向的是一个空结点,里面对应的线程为 null,且 waitStatus = 0
;
for 循环会再次走到 注释【1】
,t 赋值为 tail,所以注释【2】
条件不成立,走到 注释【5】
,即 线程B 的prev
指向t
也就是指向 tail
,如下图:
接着走到注释【6】
通过cas 操作让 tail 指向 线程B对应的 node 结点。
注释【7】的作用就是 让 t
的 next
指针指向 结点 node,然后返回 node 结点,如下:
到此,addWaiter(Node mode)
分析完成了,下面就说说acquireQueued(final Node node, int arg)
,代码如下:
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
final Node p = node.predecessor(); // 注释【1】
if (p == head && tryAcquire(arg)) { // 注释【2】
setHead(node); // 注释【3】
p.next = null; // help GC
failed = false;
return interrupted;
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())// 注释【4】,线程在这里阻塞,到时候唤醒的时候会接着走for循环
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
注意,这里面也是一个无限循环操作,此时线程B已经在等待队列里面了:
注释【1】:此时 node 结点的前驱结点是 head
,所以执行 tryAcquire
,由于线程A执行耗时的操作,暂时不会释放锁,所以 注释【2】的 tryAcquire
条件不满足。走到注释【4】。
注释【4】:包含 shouldParkAfterFailedAcquire
和 parkAndCheckInterrupt
我们逐一分析,先看 shouldParkAfterFailedAcquire
,方法如下:
private static boolean shouldParkAft
erFailedAcquire(Node pred, Node node) {
int ws = pred.waitStatus;
if (ws == Node.SIGNAL) // 注释【1】
/***
* 表示已经设置为SIGNAL,那么在这个pred结点释放锁的时候
* 能够唤醒pred的后继结点对应的线程
*/
return true;
if (ws > 0) {
// 表示前驱结点被取消了,那么从pred结点开始往前,
// 从等待队列中删除所有已经取消的结点
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else { // 注释【2】
// 通过 cas 把pred结点的waitStatus 设置为 SIGNAL
// 只有设置为SIGNAL后,才能唤醒后续结点
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
接着来, 线程B
对应结点的 前驱结点是 head
结点,前面说过了,head 结点的 waitStatus 的值为0,所有会走到 注释【2】,设置 head 的 waitStatus = -1(SIGNAL = -1),返回 false,返回到 acquireQueued
方法中,继续执行 for 循环,由于线程A 依然没有释放锁,还是走到 shouldParkAfterFailedAcquire方法,此时 pred 的waitStatus = SIGNAL,所已返回true,接着执行 acquireQueued
中的 parkAndCheckInterrupt
方法了,如下:
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);
return Thread.interrupted();
}
这个方法很简单,调用LockSupport.park阻塞 线程B
。
以上,执行完成后,head 的 waitStatus 的值为 -1。如下图:
到此,线程B 的 获取锁的操作执行完毕,被阻塞了,现在轮到 线程C
来请求锁,流程是一样的,还是最终走到这里:
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
由于线程A未释放锁,所以 线程C 尝试获取锁tryAcquire
失败,接着走 addWaiter
,此时就是将 线程C 对应的结点插入到 等待队列的尾部,插入后如下图:
然后 走到 acquireQueued
,前面分析过过代码,这里就简单说下结果。这里会先判断 线程C
结点的前驱结点是不是 head
结点,显然不是,线程C
结点的前驱结点是 线程B
对应的结点,所以会走到 shouldParkAfterFailedAcquire(pred, node)
,同样经过2次循环把 线程B
对应的结点的waitStatus值设置为-1
,然后阻塞线程C
。如下图所示:
同样,线程D的操作和线程C的操作是一样的,执行完获取锁的流程后如下图:
从上面的分析可知,ReentrantLock 所形成的等待队列非 tail
结点的 waitStatus = -1;
ReentrantLock 的 lock
方法总结:
- 当一个线程获取锁后,会将 state 置为1,然后 设置
exclusiveOwnerThread
为当前线程 - 如果当前线程再去获取锁的话,会把 state 加1 ,即锁重入
- 其他线程再去获取锁,如果锁未被其他线程释放,那么会把此线程对应的结点插入等待队列的末尾,然后判断此线程对应结点的前驱结点是否是
head
结点,如果是 head 结点,那么会尝试获取锁,获取到锁就直接return,未获取到锁,会把前驱结点的waitStatus
值设置为SIGNAL(-1),然后阻塞此线程;如果不是head结点,同样的逻辑 ,把前驱结点的waitStatus
值设置为SIGNAL(-1),然后阻塞此线程,对应如下的流程图:
2. unlock()
unlock()
的代码如下:
public void unlock() {
sync.release(1);
}
release(int arg)
调用的是 AQS 里面的逻辑,如下:
public final boolean release(int arg) {
// 调用 ReentrantLock 的内部列 Syn 中的 tryRelease
if (tryRelease(arg)) {
Node h = head;
// 成功释放锁,waitStatus != 0表示有后继结点需要唤醒
if (h != null && h.waitStatus != 0) // 注释【1】
unparkSuccessor(h); // 唤醒后继结点 注释【2】
return true;
}
return false;
}
tryRelease
是 调用 ReentrantLock 的内部列 Syn 中的 方法,如下:
protected final boolean tryRelease(int releases) {
int c = getState() - releases;
// 判断如果执行这个方法的线程不是获取锁的线程则抛出 IllegalMonitorStateException
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
// c == 0 表示释放锁成功,c 不等于0的情况比方说有2个方法f1,f2,都对应同一个ReentrantLock 锁,且f1中调用了f2
// 那么线程1执行f1然后接着执行f2,此时就是可重入锁,导致state=2,所以释放f2方法的锁时state -1 = 1,直到释放f1方法锁时,state才等于0
if (c == 0) {
free = true;
// 把独占线程设置为null
setExclusiveOwnerThread(null);
}
// 通过 cas 操作设置 state 的值。
setState(c);
return free;
}
tryRelease
的方法很简单,上面注释的很清楚;接着看 release
方法中的注释【1】,if (h != null && h.waitStatus != 0)
,我们还是通过之前的例子来加以说明,假设此时线程A
执行完任务调用unLock
来释放锁,tryRelease
执行完之后 state 值变成0,上面的B、C、D执行完的结果图再贴下:
可知 head != null,且 head 的 waitStatus = -1,所以 if (h != null && h.waitStatus != 0)
条件成立,执行后续的唤醒操作 unparkSuccessor(h)
,如下:
private void unparkSuccessor(Node node) {
// 这里的 node 指的是 head 结点,并不是待唤醒打的结点
int ws = node.waitStatus;
// 如果 ws < 0,那么 通过cas把ws设置为0,表示该结点的后继结点已经被我唤醒了
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0); // 注释【1】
Node s = node.next;
// 因为 node 指的是 head 结点,所以要唤醒的是 head 的后继结点
// 如果 待唤醒的结点是 null,或者 被取消了 waitStatus大于0 只有一种情况CANCELLED=1
if (s == null || s.waitStatus > 0) { // 注释【2】
s = null;
// 从后向前遍历找到最靠近head且未被取消的结点,为何是从后往前呢?
// 这种情况针对的是 Semaphore等,因为多个线程可能同时释放锁,
// 从前往后遍历可能前面的结点对应的线程已经被唤醒了
for (Node t = tail; t != null && t != node; t = t.prev)
if (t.waitStatus <= 0)
s = t;
}
if (s != null)// 注释【3】
LockSupport.unpark(s.thread); // 调用 unpark 唤醒被挂起的线程
}
线程A 释放锁,走到 unparkSuccessor
的时候,由于 head 的 waitStatus = -1,所以通过 cas 操作 把 waitStatus 设置为0,如下图:
head 结点的 next 结点是线程B
对应的结点,所以 if (s == null || s.waitStatus > 0)
不成立,直接走到注释【3】来唤醒线程B 对应的结点 LockSupport.unpark(s.thread)
,前面说过,一个线程被唤醒后要执行的代码是之前被阻塞那里的for循环,我把之前的代码再复制一遍,如下:
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
final Node p = node.predecessor(); // 注释【1】
if (p == head && tryAcquire(arg)) { // 注释【2】
setHead(node); // 注释【3】
p.next = null; // help GC
failed = false;
return interrupted;
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())// 注释【4】,线程在这里阻塞,到时候唤醒的时候会接着走for循环
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
上面的注释【4】表示是之前线程被挂起的地方,被唤醒后,接着进行for循环,接着走到注释【1】,结点B
的前驱结点 就是 head , 所以注释【2】的 p == head
,由于线程A已经释放了锁,所以 tryAcquire(arg)也成立,tryAcquire
会把state置为1且设置线程B为独占线程(exclusiveOwnerThread
);
接着执行 注释【3】,setHead(node)
即把结点B设置为 head 结点,如下:
private void setHead(Node node) {
head = node;
node.thread = null;
node.prev = null;
}
先把 head 指向 结点B,然后把结点B的thread属性设置为null,结点B 的prev指向null,如下图:
p.next = null
的操作之就是断开老head和新的head结点的连接执行完成后的结果如下:
此时线程B就可以执行它的任务啦,执行完成后就会唤醒后继的结点C。
现在总结下 unLock()
方法。
- 线程释放锁的时候,会把 state 减 1,然后判断 state 是否等于 0,等于则表示释放锁成功,且设置
exclusiveOwnerThread = null
; - 线程被唤醒后会被设置为新的 head 结点,且会把 thread属性置为null;
以上,ReentrantLock 的 lock
和 unLock
都已经分析完了,下面看看相关联的 Condition 的 await
和 signal
的原理。
三、Condition 的 await 和 signal 原理:
1. Condition 的await 和 signal与 Object 的 wait 和 notify 的区别:
-
Object.
wait
是随机唤醒的,比如说有4个线程 A,B,C,D 都调用了 wait 方法被挂起了,当其他线程调用notify
来唤醒被挂起的线程时,被唤醒的线程是随机的,可能是A,B,C,D中的任意一个。
Condition.signal
唤醒的是最先调用await
方法挂起的线程。 -
Condition.
signal
执行线程唤醒,并不是立即唤醒的,如果等待队列中有任务,会等到前面的任务都执行完才会接着执行唤醒线程的任务。
举个例子:假设有4个线程A、B、C、D,A先获取锁执行耗时操作,然后 B、C、D被插入等待队列,A释放锁后唤醒B,B执行 await 操作后释放锁且阻塞自己,C获取锁后调用 signal 唤醒线程B,可以看到 B被唤醒后并没有立即执行,而是等D线程任务执行完成之后才执行唤醒后的操作。测试代码如下:
public class TestCondition {
public static void main(String[] args) throws InterruptedException {
ReentrantLock lock = new ReentrantLock();
Condition condition = lock.newCondition();
Thread aThread = new Thread(new MThread(lock, condition));
aThread.setName("[A]");
Thread waitThread = new Thread(new WaitThread(lock, condition));
waitThread.setName("[B]");
Thread signalThread = new Thread(new SignalThread(lock, condition));
signalThread.setName("[C]");
Thread dThread = new Thread(new MThread(lock, condition));
dThread.setName("[D]");
aThread.start();
Thread.sleep(100);
waitThread.start();
signalThread.start();
dThread.start();
}
}
class MThread implements Runnable{
private final ReentrantLock lock;
private final Condition condition ;
public MThread(ReentrantLock lock, Condition condition) {
this.lock = lock;
this.condition = condition;
}
@Override
public void run() {
try {
lock.lock();
System.out.println(Thread.currentThread().getName()+" 开始执行等待3s , time = " + System.currentTimeMillis());
Thread.sleep(3_000);
System.out.println(Thread.currentThread().getName()+" 执行结束, time = "+ System.currentTimeMillis());
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
/**
* condition 的 await 的操作:
* 1 .new Node(Thread.currentThread(), Node.CONDITION) 插入条件队列的尾部
* 2. 如果等待队列中有其他线程结点,则唤醒后续的一个结点
* 3. 阻塞当前线程
*
* signal 操作:
* 1. 把这个待唤醒的线程结点插入等待队列的尾部
* 2. 唤醒这个结点的线程;分如下2种情况:
* 如果等待队列里面没有没有其他的结点,那么该线程会获取锁然后执行逻辑;
* 如果队列的前面还有其他的等待线程,那么要等等待队列前面的先执行完才轮到自己
*/
class WaitThread implements Runnable {
private final ReentrantLock lock;
private final Condition condition ;
public WaitThread(ReentrantLock lock, Condition condition) {
this.lock = lock;
this.condition = condition;
}
@Override
public void run() {
try {
lock.lock();
System.out.println(Thread.currentThread().getName()+" >>>>开始执行 await,等待被唤醒 , time = " + System.currentTimeMillis());
condition.await();
System.out.println(Thread.currentThread().getName()+" >>>>被唤醒 执行结束, time = "+ System.currentTimeMillis());
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
class SignalThread implements Runnable {
private final ReentrantLock lock;
private final Condition condition ;
public SignalThread(ReentrantLock lock, Condition condition) {
this.lock = lock;
this.condition = condition;
}
@Override
public void run() {
try {
lock.lock();
System.out.println(Thread.currentThread().getName()+" --- 开始执行 signal,去唤醒其他线程 , time = " + System.currentTimeMillis());
condition.signal();
System.out.println(Thread.currentThread().getName()+" --- 执行结束, time = "+ System.currentTimeMillis());
} finally {
lock.unlock();
}
}
}
执行的结果如下:
为什么是这样执行的呢?且看下面的分析:
1. await 的代码如下:
public final void await() throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
// 先把当前线程加入到condition队列中
Node node = addConditionWaiter();
// 释放当前线程持有的锁
int savedState = fullyRelease(node);
int interruptMode = 0;
// 这个方法不用管,一般返回false
while (!isOnSyncQueue(node)) { // 注释【1】
// 挂起当前的线程
LockSupport.park(this);
// 判断在挂起的时候是否被中断
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0) // 注释【2】
break;
}
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
interruptMode = REINTERRUPT;
if (node.nextWaiter != null) // clean up if cancelled
unlinkCancelledWaiters();
if (interruptMode != 0)
reportInterruptAfterWait(interruptMode);
}
addConditionWaiter
的代码如下:就是将当前线程包装成 Node,然后插入 con
dition 队列的尾部:
private Node addConditionWaiter() {
Node t = lastWaiter;
// 如果 lastWaiter 被取消了,则清除被取消的结点
if (t != null && t.waitStatus != Node.CONDITION) {
unlinkCancelledWaiters();
t = lastWaiter;
}
// 当前线程包装成 Node ,waitStatus 设置为 CONDITION
Node node = new Node(Thread.currentThread(), Node.CONDITION);
// 下面就类似于单链表的尾插法,把当前结点插入链表尾部
if (t == null)
firstWaiter = node;
else
t.nextWaiter = node;
lastWaiter = node;
// 返回当前 node
return node;
}
fullyRelease(node)
,就是释放当前线程对应的锁:
final int fullyRelease(Node node) {
boolean failed = true;
try {
int savedState = getState();
if (release(savedState)) {
failed = false;
return savedState;
} else {
throw new IllegalMonitorStateException();
}
} finally {
if (failed)
node.waitStatus = Node.CANCELLED;
}
}
就看下 fullyRelease 中的 release(int arg)
方法就行了,这个方法之前分析了,这里就说下结论:
- 当线程B释放锁的时候,会通过
release
方法把线程C 设置为 head 结点,且唤醒线程C; - 然后线程B 会执行 LockSupport.park(this); 挂起自己
下面线程C 获取锁执行 signal 唤醒线程B:
signal :
public final void signal() {
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
Node first = firstWaiter;
if (first != null)
doSignal(first);
}
从上面的 first = firstWaite 可知,signal 是从第一个开始唤醒,即哪个线程先执行 await 就先唤醒哪个线程,这是前面说的和Object.wait()第一个区别。
doSignal
如下:
private void doSignal(Node first) {
do {
// 让 firstWaiter执向下一个结点,如果下一个结点为null,则lastWaiter赋值为null
// 即 firstWaiter 和 lastWaiter 都等于 null,即没有其他线程在 condition队列中
if ( (firstWaiter = first.nextWaiter) == null)
lastWaiter = null;
first.nextWaiter = null;
} while (!transferForSignal(first) &&
(first = firstWaiter) != null);
}
transferForSignal
如下:
final boolean transferForSignal(Node node) {
// 如果 线程任务被取消,即线程被用户执行中断了,如果没有被取消,那么 设置waitStatus = 0
if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
return false;
// 把 结点插入到等待队列的尾部,注意返回的 p 是之前的尾部结点
Node p = enq(node);
int ws = p.waitStatus;
// ws > 0 表示之前的尾部结点被取消了,设置 p 的waitStatus 为 SIGNAL,
// 表示p结点后面有结点等待被唤醒,即刚插入等待队列尾部的结点 node
// 如果被取消,或者设置waitStatus 失败则唤醒node对应的线程,
// 这个很容易理解吧,前面的结点取消了,那就直接轮到自己了,所以唤醒
if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
LockSupport.unpark(node.thread);
return true;
}
这个方法注释的很清楚了,总结就是下面几点:
- 把 waitStatus 从CONDITION 变成0,因为要插入等待队列的尾部,而尾部的结点只是等待被唤醒,所以不需要设置生 SIGNAL或者其他。
- 通过
enq
方法把结点插入等待队列的尾部,返回的是node的前驱结点也就是老的tail结点 - p 表示的是老的tail结点,node 是新的tail结点,所以要把 p 的
waitStatus
设置为Node.SIGNAL,表示后继结点被LockSupport.park()
挂起了,需要被唤醒。如果 p 被取消了,或者设置 waitStatus 失败,则直接唤醒结点 node。 这也就是为什么说,signal 去唤醒线程,不会立即执行,而是要等到等到队列中的线程全部执行完成—因为signal 唤醒的时候把之前await 的线程插入到等待队列的尾部了。
这也解释了,为什么线程C执行signal 去唤醒线程B,会先执行线程D的任务,D的任务执行完之后才会执行线程B的任务了。
好了,本篇文章就结束了,有什么问题大家一起交流哈!