Condition 的 waitSet 队列与 AQS 同步队列独立且联动:await() 使线程释放锁、入条件队列并挂起;signal() 或中断触发节点迁移回同步队列争锁;条件队列为单向链表,各 Condition 独立,需 while 循环防虚假唤醒。

Condition 的 waitSet 队列(即条件队列)和 AQS 同步队列是两个独立但联动的结构,线程在二者间的转移不是自动搬运,而是由明确的语义控制:await() 主动退出同步队列、进入条件队列;signal() 或中断则触发节点从条件队列“迁移”回同步队列,重新参与锁竞争。
await():线程如何离开同步队列、进入条件队列
调用 await() 前,线程必须已持有锁——这意味着它当前是同步队列中的有效节点(通常是头节点或已 acquire 成功的节点)。整个过程分三步:
-
彻底释放锁:通过
fullyRelease(node)将 state 归零(包括所有可重入次数),并唤醒同步队列中下一个等待者。此时该线程不再持有锁,也从同步队列逻辑上“消失”。 -
构造条件节点:新建一个
Node,waitStatus = Node.CONDITION,用nextWaiter指针链入 ConditionObject 的单向链表尾部(addConditionWaiter())。这个链表就是 waitSet 队列。 -
挂起等待:调用
LockSupport.park(this)进入阻塞。此时线程只属于条件队列,与同步队列无关联。
signal():节点如何从 waitSet 搬回同步队列
signal() 不直接唤醒线程,只做一次“状态切换 + 入队”操作:
AI 舌诊面诊问诊医疗系统 + 诊疗商城系统源码是一套结合 AI 辅助诊断与商品转化的健康类程序,基于浩创云、ThinkPHP(PHP)开发,支持微信服务号与 H5 端,适合做中医舌诊面诊问答、健康建议推送以及配套诊疗商品的运营。
- 取出条件队列头节点(
firstWaiter),用 CAS 将其waitStatus从CONDITION改为 0(表示脱离条件语义)。 - 调用
enq(node)把该节点插入 AQS 同步队列尾部。注意:插入的是**同一个 Node 对象**,只是状态和位置变了。 - 插入成功后,立即调用
LockSupport.unpark(node.thread)唤醒线程。被唤醒的线程会从 park 处返回,接着执行isOnSyncQueue(node)自检——此时它已在同步队列中,跳出 await 循环,进入acquireQueued()流程争锁。
中断与 signal 的竞争:谁来完成转移?
线程在 park 状态下可能被 signal 唤醒,也可能被中断唤醒。二者都导致 while (!isOnSyncQueue(node)) 循环退出,但后续处理不同:
- 若仅被 signal 唤醒:
checkInterruptWhileWaiting()返回 0,转移由 signal 线程完成,当前线程直接走 acquire 流程。 - 若被中断唤醒:
checkInterruptWhileWaiting()返回THROW_IE或REINTERRUPT,当前线程会主动调用acquireQueued(node, savedState),自己把节点插进同步队列——相当于“中断 ≈ 隐式 signal”,但绕过条件队列头部,不依赖 signal 线程。 - 若 signal 和中断几乎同时发生:两者都尝试修改节点状态,靠 CAS 决出胜者;失败方会放弃转移,由成功方完成入队。
关键设计细节与常见误区
这些机制背后有几处容易忽略但影响行为的关键点:
- 条件队列是单向链表(靠
nextWaiter),同步队列是双向链表(靠prev/next)——这是结构差异,也是复用同一 Node 类却需区分使用方式的原因。 - 每个 Condition 实例拥有独立的 waitSet 队列;一个 ReentrantLock 可创建多个 Condition,对应多个等待场景,互不干扰。
-
IllegalMonitorStateException一定是因为没在 lock 持有状态下调用 await(),本质是同步队列前提缺失,不是条件队列本身问题。 - 虚假唤醒必须防护:await 外层必须用
while (condition != true)而非if,因为 park 可能因系统信号返回,不代表条件真的满足。










