Java中Condition的signal/await协作基于AQS双队列模型:await()释放锁并入条件队列后阻塞,signal()将条件队列头节点转移至同步队列竞争锁;必须用while循环重检条件以防虚假唤醒。

Java 中 Condition 的 signal 和 await 协作机制,本质是基于 AQS(AbstractQueuedSynchronizer)的双队列模型:一个同步队列(用于锁竞争),一个条件队列(用于等待特定条件)。二者不是独立运行,而是通过“释放锁→入条件队列→被唤醒→入同步队列→重新抢锁”这一闭环协同工作。
await() 执行时发生了什么
调用 await() 的线程必须已持有对应 Lock,否则抛出 IllegalMonitorStateException。其核心动作包括:
- 将当前线程封装为 Node,以 CONDITION 状态追加到条件队列尾部(FIFO)
- 完全释放当前持有的锁(调用 fullyRelease),并唤醒同步队列中后续可能阻塞的线程
- 线程进入阻塞状态(LockSupport.park),不再参与锁竞争,只等待 signal 或中断
- 被唤醒后,并不立即执行业务逻辑,而是先自旋尝试重新获取锁(acquireQueued),成功后才从 await() 返回
signal() 唤醒时的关键步骤
signal() 不是简单地“叫醒一个线程”,而是一次有状态迁移的队列转移操作:
- 检查调用线程是否已持有锁,未持有则直接抛异常
- 取出条件队列头节点(即等待最久的线程节点)
- 将该节点 waitStatus 从 CONDITION(-2)更新为 0(表示已离开条件队列)
- 调用 enq() 方法将其插入同步队列尾部,成为锁竞争者之一
- 若其前驱节点状态合适(非取消、非 SIGNAL),则尝试设置前驱为 SIGNAL;否则直接 unpark 该线程,促使其提前醒来重试入队
为什么必须用 while 而不是 if 检查条件
虚假唤醒(spurious wakeup)是底层系统或 JVM 实现导致的非 signal 触发的唤醒。即使没有 signal,park() 也可能返回。因此:
立即学习“Java免费学习笔记(深入)”;
- 业务条件必须在 await() 外层用 while 循环判断,例如
while (queue.isEmpty()) notEmpty.await(); - 每次从 await() 返回,都需重新验证条件是否真正满足,避免逻辑错误
- 这与 Object.wait() 的使用规范一致,是并发编程的强制约定,不是可选项
signalAll() 与 signal() 的区别仅在遍历范围
signalAll() 并非“同时唤醒所有”,而是按顺序将条件队列中每个有效节点逐个执行 transferForSignal 流程:
- 同样逐个更新 waitStatus、移入同步队列
- 每个节点仍需独立竞争锁,不存在并发执行风险
- 适用于需要广播通知的场景(如关闭信号、重置状态),但会带来更高调度开销


















