AQS条件队列与LockSupport协同实现按需精确唤醒:ConditionQueue是独立双向链表,await()将线程封装为CONDITION节点入队并park;signal()仅迁移头节点至同步队列尾部,待其成为首节点且前驱为head时,由前驱线程调用LockSupport.unpark()定向唤醒,确保一对一、无误唤醒。

AQS 的条件队列(ConditionQueue)与 LockSupport 协同工作,实现的是“按需唤醒单个或多个等待线程”,而非简单地 notifyAll。这种精确唤醒依赖于条件队列的独立结构、节点状态控制和 LockSupport.unpark() 的定向调用,不是靠对象监视器机制,而是纯用户态线程调度控制。
条件队列是独立于同步队列的双向链表
AQS 中每个 ConditionObject 实例都维护自己的条件队列,节点类型为 Node.CONDITION。这些节点不进入主同步队列(即 waitStatus == Node.CONDITION),彼此通过 nextWaiter 链接,形成一个等待特定条件的专属队列。
- 调用
condition.await()时,当前线程被封装为 CONDITION 节点,加入该条件队列尾部,并主动释放锁(调用release) - 随后线程通过
LockSupport.park()自挂起,不参与 CPU 竞争,也不释放任何 monitor - 此时线程处于 WAITING 状态,且只响应自己被
unpark()的信号
signal() 不唤醒线程,而是迁移节点到同步队列
condition.signal() 的核心动作不是直接 unpark,而是把条件队列头节点(第一个等待者)从条件队列“剪切”出来,插入到 AQS 主同步队列的尾部,并将其 waitStatus 设为 0(表示已就绪参与锁竞争)。
- 迁移后,该节点变成普通同步队列节点,但尚未被唤醒
- 真正唤醒发生在后续的
acquireQueued流程中:当该节点成为同步队列首节点且前驱是 head 时,会尝试tryAcquire;若失败,则继续 park;若成功,则流程继续 - 关键点:只有在它被调度到同步队列前端并准备抢锁时,
LockSupport.unpark()才由前驱节点(通常是刚释放锁的线程)触发 —— 这才是精确唤醒的发生时刻
LockSupport 是唤醒的执行载体,无状态、一对一
LockSupport 本身不维护线程状态或队列,只是一个轻量级阻塞/唤醒工具:
-
park()让当前线程阻塞,直到收到属于它的 permit(可由任意线程发放)或被中断 -
unpark(Thread)向指定线程发放一个 permit;若该线程正在 park,则立刻唤醒;否则 permit 保留至下次 park 使用 - AQS 在
unparkSuccessor(如unlock或signal后传播)中调用LockSupport.unpark(node.thread),目标明确,不会误唤醒其他线程
精确性来自三重隔离
为什么不会唤醒错线程?根本原因在于三个层面的隔离设计:
-
队列隔离:不同 condition 对象有各自条件队列,
signal()只操作本队列 - 节点隔离:CONDITION 节点迁移前不参与同步竞争,迁移后才进入 lock 抢占逻辑
-
线程隔离:
LockSupport.unpark(t)明确传入 thread 引用,唤醒目标唯一确定
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











