简介
在上一篇《AQS》ConditionQueue 介绍中提到了 ConditionQueue 是借助 AQS 的 SyncQueue 结构来实现条件变量及等待的功能。
ConditionQueue 是 AQS 中相对特殊、复杂的队列。相比较 SyncQueue 只把资源争抢、线程通信全部在 AQS 中处理,那ConditionQueue 就是为了对标 Object#wait、notify、notifyAll 而设计的数据结构,就是为了让开发人员可以手操线程同行。
队列结构
ConditionQueue 是基于 SyncQueue的,或者说都是基于 Node 结构的。而它的操作入口,相信接触过 JUC 的同学应该都知道,那就是 Condition
接口。
AQS 中的相关方法都在 ConditionObject
,该类正是实现了 Conditon
接口。
public interface Condition {
// 当前线程进入等待状态直到被唤醒或者中断
void await() throws InterruptedException;
// 当前线程进入等待状态,不响应中断,阻塞直到被唤醒
void awaitUninterruptibly();
// 当前线程进入等待状态直到被唤醒或者中断,阻塞带时间限制
long awaitNanos(long nanosTimeout) throws InterruptedException;
// 当前线程进入等待状态直到被唤醒或者中断,阻塞带时间限制
boolean await(long time, TimeUnit unit) throws InterruptedException;
// 当前线程进入等待状态直到被唤醒或者中断,阻塞带时间限制
boolean awaitUntil(Date deadline) throws InterruptedException;
// 唤醒单个阻塞线程
void signal();
// 唤醒所有阻塞线程
void signalAll();
}
Condition
可以说目前只有唯一的实现类,那就是 AQS 的 ConditionObject
,JUC 中的涉及到 Condtion
都是直接使用该类。
上一篇提到 AQS-Node 的核心字段中,一直有个字段nextWaiter
没有"真正"被使用到,现在终于被排上用场了。
// 在 condition 上等待的下个结点(独占模式),或者为 SHARED,标识为共享模式,不会被使用
// 为null,就是EXCLUSIVE
Node nextWaiter;
因为在一般使用中,我们仅仅是将其作为标记使用,但是当我们创建了 Condition 类(比如 ReentrantLock.newCondition
)之后,就是用来组成 Condition 上的等待队列。
注意
方法总览
上文提到,ConditionQueue 是重用 SyncQueue 的结构,并且 ConditionObject 作为 AQS 的父类,所以在 ConditionObject 的源码中,实际有不少使用 AQS 的方法,尤其是 SyncQueue 和 Node 相关。
ConditionObject 自己实现方法则一般是这三类:
- 为了提供给 Condition 接口的重写方法,以便复用;
- 队列相关操作方法,涉及到 ConditionQueue 独有的结构,比如 firstWaiter、lastWaiter;
- 基于 Condition Queue的线程统计与操作方法。
以此,我们画出 ConditionObject 的方法总览图。
总的来看,核心方法还是比较简单的。注意第2、3层会有使用到 AQS 的方法,主要是涉及到从 ConditionQueue 流转到 SyncQueue,后面会总结什么情况下会出现这样的流转。
阻塞(await)
每个调用 await 阻塞的线程,必然是已经持有锁或者说资源的。我们可以类比 Object.wait() 只能在 synchronized 方法或代码块中调用一样。
因此,基于这个前提,await方法大致主流程如下:
- 1、如果当前线程被中断,则抛出 InterruptedException。
- 2、当前线程包装为结点,加入 ConditionQueue
- 3、保存 getState 返回的锁定状态。
- 3.1、以保存状态作为参数调用 release ,如果失败则抛出 IllegalMonitorStateException。
- 4、阻塞,直到发出信号或被中断为止。
- 5、通过调用以保存状态作为参数的 acquire 专用版本来进行重新 acquire 。
- 6、临走前做一次“无效”结点的清理
- 7、如果在步骤4中被阻止而被中断,则抛出 InterruptedException
可能在不同方法之间,有些许差别。比如响应中断,对于 awaitUninterruptibly 是没有的。
1.API层
对于阻塞,其实无非就是对资源、ConditionQueue 出入、SyncQueue 出入
的一个编排,代表了对资源的争抢和放弃的流转。
实际流程都是大同小异的,下面就以 await() 举例。
注意,awaitUninterruptibly() 是特殊的,因为不响应中断,所以必然不会因为中断而进入 Sync Queue。后面会看下源码。
然后看看代码,哪些是操作 ConditionQueue、哪些是SyncQueue,剩下的就是线程操作了。
public final void await() throws InterruptedException {
// 1.首先检查中断
if (Thread.interrupted())
throw new InterruptedException();
// 2.添加到条件队列
Node node = addConditionWaiter();
// 3.释放资源, 返回原有锁状态
int savedState = fullyRelease(node);
int interruptMode = 0;
while (!isOnSyncQueue(node)) {
// 4.如果不在 等待队列 上, 就继续阻塞
// 因为被唤醒时, 结点会被转移到 等待队列 上
LockSupport.park(this);
// 4-1.校验中断:如果是中断了, 会被转到 等待队列 上
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
break;
}
// 5.退出阻塞(中断或者被唤醒), 说明已经在队列, 重新循环获取资源了
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
interruptMode = REINTERRUPT;
// (因为还在 Lock 代码中, 所以必须拿到锁才能往后执行吧)
// 6.临走前做一次清理(包括自己), 否则就是等着被人处理
if (node.ne