ReentrantLock公平锁通过AQS的CLH队列实现严格FIFO:每次获取锁前调用hasQueuedPredecessors()检查队列中是否有更早等待者,有则排队;入队CAS保证顺序,唤醒仅针对head.next,确保逻辑上先到先服务。

ReentrantLock 通过内部的 AQS(AbstractQueuedSynchronizer) 实现公平锁的严格 FIFO 队列,核心在于:公平模式下,每次尝试获取锁前,先检查同步队列中是否有等待更久的线程;若有,则当前线程必须排队,不能插队。
公平锁的关键机制:acquire() 中的 hasQueuedPredecessors()
公平锁的 lock() 方法最终调用 AQS 的 acquire()。与非公平锁不同,公平锁的 tryAcquire() 实现中会调用 hasQueuedPredecessors() 判断是否该让步:
- 该方法检查 AQS 同步队列中是否存在比当前线程更早等待的节点(即 head.next 是否存在且不是当前线程)
- 只要队列中有等待者(哪怕还没被 park),当前线程就放弃抢锁,直接进入队列尾部
- 这从源头上杜绝了“刚唤醒或刚调用 lock() 就抢到锁”的插队行为
CLH 队列保证插入和唤醒的顺序性
ReentrantLock 使用 AQS 维护的双向 CLH(Craig, Landin, and Hagersten)变体队列实现 FIFO:
你接手一个需求时,是不是经常这样:"改一个小字段 → 结果把不相关的代码也改了"、"需求说得很模糊 → 做着做着发现跑偏了"、"数据库加个字段 → 上线才发现漏了数据兼容"、"做了个新功能 → 一个月后自己都忘了当时怎么设计的"、"写到一半被中断 → 再回来不知道从哪继续";这个 Skill 就是专门解决这些问题的。 它会自动探测你的项目环境(JDK、Spring Boot、ORM、数据库、前端框架),记在
- 新线程入队时,总是通过 CAS 原子地追加到队尾(tail 指针更新),天然保持插入顺序
- 释放锁时,只唤醒 head.next 对应的节点(即队首等待者),唤醒顺序与入队顺序完全一致
- 每个节点的 waitStatus、prev/next 指针由线程安全操作维护,避免重排序或丢失通知
park/unpark 与状态流转配合队列调度
线程在队列中并非一直忙等,而是进入阻塞态,依赖精确的 unpark 唤醒:
立即学习“Java免费学习笔记(深入)”;
- 未获取锁的线程调用 LockSupport.park() 进入 WAITING 状态,CPU 资源释放
- 持有锁的线程 unlock() 时,AQS 找到 head.next 节点并调用 LockSupport.unpark() 唤醒它
- 被唤醒线程重新执行 tryAcquire(),此时它已是队首,且无更早等待者,可成功获取锁
注意:FIFO 是队列级的,不是绝对时间意义上的“毫秒级先后”
公平性保障的是逻辑上的排队顺序,而非纳秒级精确:
- 两个线程几乎同时调用 lock(),因 CPU 调度、CAS 竞争微小差异,可能一个先入队、一个后入队,但不会颠倒顺序
- 只要入队成功,位置就固定;后续唤醒严格按 next 指针链表顺序进行
- 不保证线程从 park 到执行 tryAcquire 的耗时完全相等,但获取锁的权利严格按队列位置授予

















