AQS通过FIFO双向同步队列和state状态变量实现线程等待与唤醒:等待线程封装为EXCLUSIVE节点入队尾部,失败后park阻塞;释放锁时仅唤醒head.next节点,确保独占性和FIFO公平性,head为虚节点,真正等待者始于head.next。

AQS(AbstractQueuedSynchronizer)是 ReentrantLock 底层实现线程等待与唤醒的核心机制。它不直接管理锁,而是通过一个 FIFO 的双向同步队列(CLH 变种)来组织等待获取锁的线程,并配合状态变量(state)和 CAS 操作,精确控制线程的阻塞与唤醒时机。
等待线程如何进入队列
当线程调用 lock() 但发现锁已被占用(即 state > 0),且当前线程不是持有锁的线程时,会尝试将自己封装为一个 Node 节点,以“独占模式”加入 AQS 的同步队列尾部:
- 节点类型为
Node.EXCLUSIVE,表示这是独占锁的等待者 - 入队前先用 CAS 尝试获取锁(快速路径),失败后才走完整入队流程
- 入队过程本身也是 CAS 保障的:先设置新节点的
prev指向原尾节点,再用 CAS 更新tail;若失败则重试,确保队列结构一致 - 成功入队后,线程会调用
LockSupport.park()进入 WAITING 状态,不再消耗 CPU
唤醒线程的触发条件与执行者
唤醒不是由持有锁的线程“主动点名”,而是由释放锁的操作自然触发的链式响应:
-
unlock()最终调用sync.release(1),先调用tryRelease()(ReentrantLock 实现)将state减到 0,返回true - 接着调用
unparkSuccessor(head):检查头节点的下一个有效(非取消)节点,并对其执行LockSupport.unpark(node.thread) - 被唤醒的线程从
park()返回,再次尝试acquireQueued()中的 CAS 获取锁——此时若成功,就成为新持有者;若失败,可能重新 park 或继续自旋
为什么只唤醒 head 的后继,而不是所有等待者
ReentrantLock 是独占锁,任意时刻最多只允许一个线程持有。AQS 的设计保证了公平性与效率的平衡:
立即学习“Java免费学习笔记(深入)”;
- 唤醒 head.next 是为了维持 FIFO 顺序(尤其在公平锁模式下),避免线程饥饿
- 即使是非公平锁,唤醒也仅限一个——因为刚释放锁后,立刻有新线程可能通过快速路径抢到锁,此时无需唤醒更多人;若没抢到,被唤醒者自然承接
- 不唤醒全部,是因为唤醒未获锁的线程只会导致它们立即再次 park,徒增上下文切换开销
- 取消节点(如线程中断或超时)会被
cancelAcquire()清理,不影响唤醒逻辑的正确性
关键细节:head 节点始终是“已获锁”的虚节点
AQS 队列中的 head 并不对应正在等待的线程,而是一个空的哨兵节点,代表“当前持有锁的线程已出队”。真正等待的是 head.next:
- 线程获取锁成功后,会把自己设为新的 head(在
setHead()中),原 head 被自然淘汰 - 所以
unparkSuccessor(head)实际唤醒的是“第一个真正等待的线程”,语义清晰、无歧义 - 这种设计让入队、出队、唤醒各阶段解耦,无需锁保护队列操作,全靠 CAS 和 volatile 字段(如
waitStatus、next)保证可见性与原子性


















