ConditionObject 是 AQS 框架内部的非静态内部类 ,它实现了 Condition 接口,为基于 AQS 的独占锁(如 ReentrantLock)提供了条件等待/通知 机制。与 Object.wait/notify 相比,它的核心优势是:一把锁可以绑定多个条件队列,实现更精细的线程调度。
一、ConditionObject 的数据结构
ConditionObject 维护一个单向链表 作为条件队列,节点复用 AQS 的 Node 类,但使用 nextWaiter 字段连接,且节点状态固定为 Node.CONDITION。
java
public class ConditionObject implements Condition {
private transient Node firstWaiter; // 条件队列头
private transient Node lastWaiter; // 条件队列尾
}
队列结构示意:
text
firstWaiter lastWaiter
↓ ↓
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ Node │ → │ Node │ → │ Node │
│ waitStatus │ │ waitStatus │ │ waitStatus │
│ =CONDITION │ │ =CONDITION │ │ =CONDITION │
│ nextWaiter │ │ nextWaiter │ │ nextWaiter │
└─────────────┘ └─────────────┘ └─────────────┘
与同步队列的关键区别:
| 特性 | 同步队列 (Sync Queue) | 条件队列 (Condition Queue) |
|---|---|---|
| 数据结构 | 双向链表 (prev + next) |
单向链表 (nextWaiter) |
| 节点状态 | SIGNAL、CANCELLED、0、PROPAGATE |
仅 CONDITION (-2) |
| 入队时机 | 争锁失败时 | 调用 await() 时 |
| 出队时机 | 获取锁成功时 | 调用 signal() 时转移到同步队列 |
| 线程状态 | 阻塞等待锁 (park) |
阻塞等待条件 (park) |
二、await() 方法详解
await() 是条件等待的核心方法。调用前提 :当前线程必须已经持有锁(即位于同步队列的 head 位置)。
2.1 完整源码分析
java
public final void await() throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
// ① 将当前线程加入条件队列尾部(状态设为 CONDITION)
Node node = addConditionWaiter();
// ② 完全释放锁(包括所有重入次数),并记录释放前的 state
int savedState = fullyRelease(node);
int interruptMode = 0;
// ③ 自旋:判断节点是否已被转移到同步队列
while (!isOnSyncQueue(node)) {
LockSupport.park(this); // 阻塞,等待 signal
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
break;
}
// ④ 被唤醒后,重新在同步队列中竞争锁
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
interruptMode = REINTERRUPT;
// ⑤ 清理条件队列中已取消的节点
if (node.nextWaiter != null)
unlinkCancelledWaiters();
// ⑥ 处理中断
if (interruptMode != 0)
reportInterruptAfterWait(interruptMode);
}
2.2 关键步骤剖析
① addConditionWaiter():加入条件队列
java
private Node addConditionWaiter() {
Node t = lastWaiter;
// 如果尾节点状态不是 CONDITION,说明已取消,先清理整个队列
if (t != null && t.waitStatus != Node.CONDITION) {
unlinkCancelledWaiters();
t = lastWaiter;
}
// 创建新节点,状态为 CONDITION(注意:没有指定模式,同步队列模式由后续转移时决定)
Node node = new Node(Thread.currentThread(), Node.CONDITION);
if (t == null)
firstWaiter = node;
else
t.nextWaiter = node;
lastWaiter = node;
return node;
}
② fullyRelease(Node):完全释放锁
java
final int fullyRelease(Node node) {
boolean failed = true;
try {
int savedState = getState();
// 调用 release(savedState) 释放所有重入次数,并唤醒同步队列的后继
if (release(savedState)) {
failed = false;
return savedState;
} else {
throw new IllegalMonitorStateException();
}
} finally {
if (failed)
node.waitStatus = Node.CANCELLED;
}
}
注意 :这里释放的是所有重入次数 ,因为 await() 必须完全释放锁才能让其他线程进入。savedState 用于后续重新获取锁时恢复重入计数。
③ isOnSyncQueue(Node):判断节点是否已转移
java
final boolean isOnSyncQueue(Node node) {
// 状态为 CONDITION 或 prev 为 null,说明肯定不在同步队列
if (node.waitStatus == Node.CONDITION || node.prev == null)
return false;
// 如果有后继节点,说明一定在同步队列(同步队列是双向链表)
if (node.next != null)
return true;
// 否则从 tail 向前遍历查找
return findNodeFromTail(node);
}
④ checkInterruptWhileWaiting(Node):处理等待期间的中断
java
private int checkInterruptWhileWaiting(Node node) {
return Thread.interrupted() ?
(transferAfterCancelledWait(node) ? THROW_IE : REINTERRUPT) :
0;
}
- THROW_IE :中断发生在
signal之前,后续抛出InterruptedException。 - REINTERRUPT :中断发生在
signal之后,仅重新设置中断状态。
⑤ acquireQueued(Node, savedState):重新竞争锁
节点被转移到同步队列后,调用此方法在同步队列中自旋/阻塞,直到重新获取到锁 ,并恢复重入计数(savedState)。
2.3 流程图
2.3.1 一个节点在两个队列间的旅程
2.3.2 await() 流程:从同步队列转移到条件队列
(持有锁状态) T->>Cond: 1. addConditionWaiter() Note over Cond: 创建新节点,状态设为 CONDITION
加入条件队列尾部 Cond-->>T: 返回节点 node T->>Sync: 2. fullyRelease(node) Note over Sync: 调用 release(savedState)
完全释放锁(包括重入次数)
唤醒同步队列中的后继节点 Sync-->>T: 返回 savedState loop 3. 自旋等待转移 T->>Sync: isOnSyncQueue(node)? alt 不在同步队列 Sync-->>T: false T->>OS: LockSupport.park(this) Note over T,OS: 线程阻塞,等待 signal 唤醒 OS-->>T: 被唤醒(signal 或中断) T->>T: checkInterruptWhileWaiting() else 已在同步队列 Sync-->>T: true Note over T: 退出循环 end end T->>Sync: 4. acquireQueued(node, savedState) Note over Sync: 在同步队列中自旋/阻塞
重新竞争锁,恢复重入计数 Sync-->>T: 获取锁成功 T->>Cond: 5. unlinkCancelledWaiters() Note over Cond: 清理条件队列中已取消的节点 Note over T: await() 返回,线程继续执行
2.3.3 signal() 流程:从条件队列转移到同步队列
(位于同步队列头部) T2->>Cond: 1. isHeldExclusively() 检查 Cond-->>T2: true T2->>Cond: 2. doSignal(firstWaiter) Note over Cond: 摘除条件队列头部节点 first T2->>Cond: 3. transferForSignal(first) rect rgb(240, 248, 255) Note over Cond,Sync: 节点状态转换与迁移 Cond->>Cond: CAS waitStatus: CONDITION → 0 Cond->>Sync: enq(node) 加入同步队列尾部 Sync-->>Cond: 返回前驱节点 p end alt 前驱已取消 或 CAS 设置 SIGNAL 失败 Cond->>OS: LockSupport.unpark(T1) OS-->>T1: 唤醒信号 Note over T1: 提前唤醒,自行处理同步队列状态 else 正常情况 Note over Cond: 不主动唤醒
等待前驱释放锁时被动唤醒 end T2->>Cond: firstWaiter 后移 Note over T2: signal() 返回
2.3.4 状态说明
| 阶段 | 节点位置 | waitStatus |
线程状态 |
|---|---|---|---|
| 初始(持有锁) | 同步队列头部 | SIGNAL(可能) |
RUNNABLE |
await() 后 |
条件队列 | CONDITION |
WAITING (park) |
signal() 后 |
同步队列尾部 | 0 → SIGNAL |
WAITING 或 RUNNABLE |
| 重新获取锁后 | 同步队列头部 | SIGNAL(可能) |
RUNNABLE |
核心要点 :节点永远不会同时存在于两个队列中 。
await()使其从同步队列消失、出现在条件队列;signal()使其从条件队列消失、出现在同步队列尾部。
三、signal() 方法详解
signal() 将条件队列中等待时间最长 的节点(firstWaiter)转移到同步队列。
3.1 完整源码分析
java
public final void signal() {
// 前提:当前线程必须持有锁
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
Node first = firstWaiter;
if (first != null)
doSignal(first);
}
java
private void doSignal(Node first) {
do {
// 将 firstWaiter 后移
if ((firstWaiter = first.nextWaiter) == null)
lastWaiter = null;
first.nextWaiter = null; // 断开与条件队列的连接
} while (!transferForSignal(first) && (first = firstWaiter) != null);
}
java
final boolean transferForSignal(Node node) {
// ① CAS 将节点状态从 CONDITION 改为 0(初始状态)
if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
return false; // 节点已被取消,跳过
// ② 将节点加入同步队列尾部,返回前驱节点
Node p = enq(node);
int ws = p.waitStatus;
// ③ 如果前驱已取消 或 CAS 设置 SIGNAL 失败,则直接唤醒线程
if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
LockSupport.unpark(node.thread);
return true;
}
3.2 关键步骤剖析
-
状态转换 :
CONDITION→0。如果 CAS 失败,说明节点已被取消(如中断),直接返回false。 -
加入同步队列 :调用
enq(node)原子地挂到同步队列尾部。 -
唤醒策略 :正常情况下,节点加入同步队列后不会立即唤醒 ,而是等待前驱释放锁时被动唤醒。但有两种例外会提前唤醒 :
- 前驱节点已取消(
ws > 0)。 - CAS 设置前驱状态为
SIGNAL失败。
此时直接
unpark线程,让它自己在同步队列中处理(重新设置SIGNAL或跳过取消节点)。 - 前驱节点已取消(
3.3 signalAll() 的区别
java
private void doSignalAll(Node first) {
lastWaiter = firstWaiter = null;
do {
Node next = first.nextWaiter;
first.nextWaiter = null;
transferForSignal(first);
first = next;
} while (first != null);
}
遍历整个条件队列,将所有节点逐个转移到同步队列。
四、中断与超时处理
4.1 中断模式分类
| 中断时机 | transferAfterCancelledWait(node) 返回值 |
最终处理 | 标记 |
|---|---|---|---|
在 signal 之前中断 |
true |
抛出 InterruptedException |
THROW_IE |
在 signal 之后中断 |
false |
仅设置中断状态,不抛异常 | REINTERRUPT |
java
final boolean transferAfterCancelledWait(Node node) {
// 尝试将状态从 CONDITION 改为 0
if (compareAndSetWaitStatus(node, Node.CONDITION, 0)) {
// 成功:说明 signal 还没执行,自己入队
enq(node);
return true; // 中断发生在 signal 之前
}
// 失败:说明 signal 正在执行,等待它完成转移
while (!isOnSyncQueue(node))
Thread.yield();
return false; // 中断发生在 signal 之后
}
设计意图 :保证先被 signal 的线程不会因为后续中断而丢失锁,从而维持条件变量的正确语义。
4.2 超时版本
ConditionObject 提供了多种超时等待方法,其实现与 await() 相似,主要差异在阻塞阶段:
| 方法 | 阻塞方式 | 返回值 |
|---|---|---|
awaitNanos(long) |
LockSupport.parkNanos |
剩余超时纳秒数,≤0 表示超时 |
awaitUntil(Date) |
计算截止时间,parkNanos |
boolean:true 表示被唤醒,false 表示超时 |
await(long, TimeUnit) |
转换为纳秒,调用 awaitNanos |
boolean |
以 awaitNanos 为例:
java
public final long awaitNanos(long nanosTimeout) throws InterruptedException {
// ... 类似 await() 的前置处理
while (!isOnSyncQueue(node)) {
if (nanosTimeout <= 0L) {
transferAfterCancelledWait(node); // 超时,取消等待
break;
}
nanosTimeout = LockSupport.parkNanos(this, nanosTimeout);
// 检查中断...
}
// ... 后续竞争锁和清理
}
五、与 Object.wait/notify 的对比
| 维度 | synchronized + wait/notify |
Lock + Condition |
|---|---|---|
| 条件队列数量 | 每个对象唯一 | 一把锁可创建多个 Condition |
| 唤醒精确性 | notify() 随机唤醒一个;notifyAll() 全唤醒 |
signal() 精确唤醒一个特定条件队列的线程 |
| 中断处理 | 抛出 InterruptedException,无法区分时机 |
区分 signal 前/后中断,保证锁语义 |
| 超时控制 | 仅 wait(long) 毫秒级 |
支持纳秒、绝对时间点等多种方式 |
| 锁持有检查 | 隐式(必须在 synchronized 块内) |
显式调用 isHeldExclusively() 检查 |
六、完整生命周期示意图
七、总结
| 核心要点 | 说明 |
|---|---|
| 数据结构 | 单向链表,复用 Node 的 nextWaiter,状态恒为 CONDITION |
await() 流程 |
入条件队列 → 完全释放锁 → 阻塞 → 被转移后重新竞争锁 |
signal() 流程 |
摘除队首节点 → 状态从 CONDITION 改 0 → 加入同步队列尾部 |
| 中断处理 | 区分 signal 前/后,保证被唤醒线程不会因中断而丢失锁 |
| 多条件支持 | 一把锁可创建多个 ConditionObject,实现精细化线程调度 |
| 与同步队列协作 | 节点在条件队列 和同步队列之间迁移,不同时存在于两边 |
ConditionObject 是 AQS 框架中组合优于继承 的典范。它复用了 AQS 的 Node 结构、enq 入队、acquireQueued 竞争、LockSupport 阻塞等底层设施,仅用约 400 行代码就实现了一个功能完整的条件变量,充分展现了 AQS 设计的扩展性和精妙性。