Java ReentrantLock的unlock方法未重置队列头,已执行线程节点如何移除?
你的理解偏差在于:AQS 队列的出队操作不由释放锁的release逻辑负责,而是由被唤醒后成功抢到锁的后继线程在锁获取流程中执行的。
核心逻辑梳理
AQS 的等待队列中,头节点默认代表已经持有锁的线程对应的节点,出队操作发生在acquireQueued方法的抢锁成功分支,对应核心源码如下:
final boolean acquireQueued(final Node node, int arg) { boolean failed = true; try { boolean interrupted = false; for (;;) { final Node p = node.predecessor(); // 前驱是头节点时尝试抢锁 if (p == head && tryAcquire(arg)) { // 抢锁成功后将自身设为新的头节点,完成旧头节点出队 setHead(node); p.next = null; // 移除旧头节点的引用,帮助GC回收 failed = false; return interrupted; } // 抢锁失败则挂起等待唤醒 if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt()) interrupted = true; } } finally { if (failed) cancelAcquire(node); } }
其中setHead方法会直接重置队列头,切断旧头节点的引用:
private void setHead(Node node) { head = node; node.thread = null; node.prev = null; }
完整流程说明
- 持有锁的线程调用
unlock触发release逻辑,释放锁资源后唤醒头节点的后继等待线程 - 被唤醒的线程从挂起状态恢复,继续执行
acquireQueued中的循环抢锁逻辑 - 抢锁成功后执行
setHead将自身设置为新的头节点,旧头节点因为没有任何可达引用会被GC回收
这种设计的优势是将队列修改逻辑收敛到抢锁成功的单线程中,不需要额外的并发控制,也降低了释放锁线程的执行开销,队列长度始终等于当前等待锁的线程数+1个当前头节点,不会出现无限增长的问题。
内容的提问来源于stack exchange,提问作者Machi
相关产品推荐
相关产品推荐

