ReentrantLock释放锁时不主动清理队列,而是通过unparkSuccessor唤醒头结点后首个有效后继;被唤醒线程获取锁后成为新头结点,原头结点因引用断开而由GC回收,取消节点则被后续操作被动跳过。

ReentrantLock 在释放锁时,并不会主动“清理”等待队列中的节点,而是通过唤醒机制让阻塞线程自行完成状态清理;真正被移出队列的,是那些成功获取锁并退出同步块的线程所对应的节点。
释放锁时的核心动作:unparkSuccessor
调用 unlock() 会触发 AbstractQueuedSynchronizer (AQS) 的 release() 流程,最终执行 unparkSuccessor(h) —— 即唤醒队列中 h 节点(头结点)之后的第一个有效(非取消)后继节点。
- 头结点本身在释放前已被标记为“已释放”,但尚未从队列中物理删除
- AQS 不主动遍历或清除整个等待队列,只负责唤醒下一个可运行的线程
- 被唤醒的线程在 acquireQueued 中尝试获取锁,成功后会把自己设为新的头结点,并调用 setHead(node)
头结点的“自然淘汰”机制
新头结点设置后,原头结点(即刚释放锁的节点)会被断开引用,其 thread 字段置为 null,prev 和 next 也变为 null,从而成为垃圾对象,等待 GC 回收。
- 这个过程不是“清理队列”,而是头结点更替带来的副作用
- 中间的取消节点(如因超时、中断而放弃等待)会在后续的 shouldParkAfterFailedAcquire 或 cleanQueue(某些 JDK 版本优化)中被跳过或惰性剔除
- 队列中仍可能残留已取消但未被跳过的节点,属于正常现象
取消节点的处理:被动跳过,非主动清理
当一个线程被中断或超时退出等待时,它会将自己的节点状态设为 Node.CANCELLED。这类节点不会被立即移除,但在以下场景中被绕过:
立即学习“Java免费学习笔记(深入)”;
- 后续线程在入队时,会向前跳过所有 CANCELLED 前驱节点
- unparkSuccessor 会向后遍历,跳过 CANCELLED 后继,寻找第一个可用节点
- 某些 JDK 版本(如 JDK 8u292+)在 unparkSuccessor 中增加了对尾部取消节点的修剪逻辑,但仍是局部、惰性的
为什么没有全局清理?设计权衡
AQS 选择延迟与按需处理,是为了避免高并发下频繁修改队列结构带来的性能开销和同步复杂度。
- 每次 unlock 都遍历整个队列清理取消节点,会显著拖慢释放路径
- 等待队列本质是链表,物理删除需 CAS 更新前后指针,竞争下易失败重试
- 实际运行中,绝大多数取消节点很快被自然覆盖或被后续操作跳过,影响有限
所以 ReentrantLock 的“清理”是隐式、渐进且以唤醒为中心的——它靠节点状态流转与头结点更替来维持队列有效性,而非显式遍历删除。


















