reentrantlock获取锁失败时线程被挂起并加入aqs的clh双向同步队列,通过locksupport.park()实现零cpu等待;公平锁严格fifo,非公平锁允许插队;支持中断响应与超时等待。

ReentrantLock 获取锁失败时,线程不会忙等,而是被挂起并加入同步队列(AQS 的 CLH 队列),直到持有锁的线程释放锁后唤醒它。
同步队列(AQS CLH 队列)是等待的核心结构
ReentrantLock 基于 AbstractQueuedSynchronizer(AQS)实现。当线程调用 lock() 但锁已被占用时,AQS 会将该线程封装为一个 Node 节点,以“尾插法”加入双向同步队列。这个队列不是操作系统级的等待队列,而是 JVM 在堆内存中维护的逻辑队列。
- 每个 Node 包含线程引用、等待状态(waitStatus)、前驱和后继指针
- 初始 waitStatus 为 0;进入等待前会被设为 Node.SIGNAL(-1),表示后续节点需要被唤醒
- 头节点(head)始终是已获取锁或即将获取锁的线程;真正阻塞的是头节点的后继节点
线程挂起依赖 LockSupport.park(),不消耗 CPU
入队完成后,当前线程会执行 LockSupport.park(this),交由 JVM 线程调度器将其置为 WAITING 状态,并从 CPU 调度器移除。该操作底层通常映射到操作系统原语(如 Linux 的 futex 或 pthread_cond_wait),不轮询、不自旋(除非启用了公平锁下的 tryAcquire 或设置了自适应自旋策略)。
- park 后线程释放所有 CPU 时间片,完全不参与调度,零 CPU 占用
- 被唤醒仅通过其他线程调用 LockSupport.unpark(thread) 触发,常见于 unlock() 中唤醒 head.next
- 注意:park 可能因虚假唤醒(spurious wakeup)返回,所以必须配合 while 循环 + 状态判断使用(AQS 内部已封装好)
公平锁与非公平锁的等待行为差异
两者都使用同一套 AQS 队列机制,关键区别在于 尝试获取锁的时机:
- 非公平锁:调用 lock() 时先直接 CAS 尝试抢锁(即“插队”),失败才入队等待。这可能导致新线程比队列中等待已久的线程更早获取锁
- 公平锁:lock() 严格遵循 FIFO,先检查队列是否为空或自己是否是队首,再尝试获取;即使锁空闲,若有前序等待者,也必须排队
- 无论哪种,一旦入队,后续等待、唤醒、超时等逻辑完全一致,均由 AQS 统一管理
中断与超时等待的特殊处理
ReentrantLock 支持响应中断(lockInterruptibly())和带超时获取(tryLock(long, TimeUnit)),它们在等待阶段引入额外状态控制:
- lockInterruptibly() 入队后调用 park 之前会检查中断状态;park 中被中断时抛出 InterruptedException,并从队列中取消该节点(设置 waitStatus = Node.CANCELLED)
- tryLock(timeout) 底层调用 doAcquireNanos(),内部使用 System.nanoTime() 计算剩余时间,超时则主动取消节点并返回 false
- 被取消的节点仍保留在队列中,但 AQS 在唤醒传播时会跳过它们,最终由后续节点的 clean-up 逻辑剔除
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











