Condition等待队列是单向链表,节点waitStatus恒为-2,与同步队列状态互斥;await()包含入等待队列、释放锁、自旋检查并park;signal()仅搬运节点至同步队列尾部并设SIGNAL,不直接唤醒;多Condition间队列完全隔离。

Condition 等待队列本质是单向链表,不是同步队列的拷贝
很多人误以为 Condition 队列和 AQS 同步队列一样是双向链表,实际它只用 nextWaiter 字段串成单向 FIFO 队列。节点的 waitStatus 固定为 Node.CONDITION(值为 -2),与同步队列中 SIGNAL(-1)、CANCELLED(1)等状态互斥。这意味着:一旦线程进入等待队列,它就彻底脱离同步队列的调度逻辑,不能再被 unpark 直接唤醒——必须先被 signal() 迁移过去。
await() 执行时线程会经历三次关键状态迁移
调用 await() 不是简单“挂起”,而是一套原子性状态切换流程:
- 先封装当前线程为
Node,通过addConditionWaiter()插入等待队列尾部(lastWaiter更新) - 立即调用
fullyRelease()彻底释放锁——这一步会把同步队列中的节点状态清空、并唤醒后继节点(否则可能造成死锁) - 进入自旋等待:检查自己是否已被转移到同步队列(即是否被
signal()过);若未转移,则调用LockSupport.park(this)挂起
注意:挂起前已无锁,挂起后也不在任何队列中“排队”——它只是个被 park 的线程,真正恢复调度依赖的是 signal() 触发的迁移动作。
signal() 不是直接 unpark,而是做节点搬运工
signal() 的核心行为是把等待队列头节点(firstWaiter)摘下,通过 enq() 插入同步队列尾部,并设置其 waitStatus 为 SIGNAL。这个过程不涉及任何线程唤醒操作:
- 被搬运的节点此时仍在阻塞态,
park()并未返回 - 只有当该节点成为同步队列首节点、且前驱节点释放锁时,才会触发
unparkSuccessor()唤醒它 - 被唤醒后,线程从
await()返回,但此时尚未持有锁——它要像普通竞争者一样重新尝试获取锁(所以必须写在while循环里)
这就是为什么 signal() 调用后,线程不会立刻执行临界区代码:它得先抢锁成功,才真正“醒来”。
多个 Condition 共享一把锁时,等待队列是完全隔离的
同一个 ReentrantLock 实例可以创建多个 Condition,比如 notFull 和 notEmpty。它们各自维护独立的 firstWaiter/lastWaiter,节点之间零交叉。这种隔离带来两个关键影响:
-
signalAll()只唤醒本 Condition 队列所有节点,不会波及其他队列 - 一个线程调用
condition1.await()后,只能被condition1.signal()唤醒,condition2.signal()对它完全无效 - 如果误用不同 Condition 的 signal/await 组合(比如生产者用
notFull.signal(),消费者却在notEmpty.await()),就会出现“永远等不到唤醒”的静默挂起
这种隔离性是比 synchronized + wait()/notify() 更精确的控制能力来源,也是最容易因命名或逻辑错配导致隐蔽 bug 的地方。










