ReentrantLock公平锁通过AQS FIFO队列+hasQueuedPredecessors()检查实现按入队顺序获取锁:队列非空时禁止抢锁,仅唤醒head.next节点,确保无插队和确定性唤醒顺序。

ReentrantLock 通过构造函数传入 true 创建公平锁,其核心在于内部使用 AQS(AbstractQueuedSynchronizer)的 FIFO 同步队列 + 公平性检查逻辑,确保线程按进入等待队列的顺序获取锁,而非依赖 CPU 调度随机抢占。
公平锁的关键机制:每次 tryAcquire 都检查队列头部
与非公平锁不同,公平锁的 tryAcquire 实现中,**在尝试获取锁前,会先判断同步队列中是否有等待更久的线程**。具体逻辑如下:
- 如果同步队列为空(
!hasQueuedPredecessors()返回 true),才允许当前线程直接 CAS 尝试获取锁; - 如果队列非空(即已有线程在排队),即使锁当前空闲,当前线程也不抢锁,而是老老实实入队等待;
-
hasQueuedPredecessors()的判断依据是:AQS 的head.next是否指向一个有效的等待节点 —— 即头结点后是否存在“排在自己前面”的线程。
排队过程:线程入队遵循 FIFO,由 AQS 自动维护
当线程因不公平条件(如队列非空或 CAS 失败)无法获取锁时,会调用 acquireQueued(addWaiter(Node.EXCLUSIVE), arg) 完成排队:
-
addWaiter将当前线程包装为Node.EXCLUSIVE节点,以 CAS 方式插入同步队列尾部; - AQS 内部的
CLH 变种队列是双向链表,保证插入和出队的原子性与顺序性; - 每个节点入队后,会自旋检查前驱节点是否为 head,只有当前驱成为 head 且成功获取锁后,自身才有机会被唤醒并尝试获取锁。
唤醒与获取:头节点释放锁后,只唤醒 next 等待者
当持有锁的线程调用 unlock() 时,会触发 AQS 的 release 流程:
立即学习“Java免费学习笔记(深入)”;
- 先调用
tryRelease释放锁状态(state 减 1,直到为 0); - 若完全释放成功,则调用
unparkSuccessor(head),唤醒head.next指向的那个等待节点(即队列中最老的等待者); - 被唤醒的线程从 park 状态恢复,在
acquireQueued中再次调用tryAcquire—— 此时它位于队首,且队列中无更早等待者,大概率能成功获取锁。
注意:公平锁不保证“绝对实时顺序”,但保证“入队顺序即执行顺序”
由于线程调度、上下文切换等系统因素,刚入队的线程可能比稍早入队但被阻塞更久的线程略晚唤醒,但 AQS 的设计确保:
- 没有线程能“插队”——新线程不会在已有等待者存在时成功 acquire;
- 唤醒严格按队列先后:head → head.next → head.next.next ……;
- 即使多个线程几乎同时到达,CAS 插入队尾和后续的唤醒链也天然形成确定性顺序。
不复杂但容易忽略:公平锁性能通常低于非公平锁,因为每次 acquire 都要多一次队列判空开销,且减少了“刚释放就命中”的机会;但它换来的是可预测的等待时间上限和无饥饿保障。


















