核心差异在于是否检查AQS队列:公平锁先调用hasQueuedPredecessors()确认无前序等待者才CAS抢锁,保证FIFO顺序;非公平锁直接CAS抢锁,失败才入队,提升吞吐但可能导致饥饿。

ReentrantLock 的公平锁与非公平锁,核心差异在于线程获取锁时是否检查同步队列(AQS 的 CLH 队列)中是否有等待者——这完全由 tryAcquire 的实现逻辑决定,而 AQS 本身不区分公平性,只是提供统一的排队、阻塞、唤醒框架。
公平锁:先到先得,严格按队列顺序
公平锁的 tryAcquire 方法在尝试获取锁前,会先调用 hasQueuedPredecessors() 判断当前线程是否是队列头节点的后继(即队列为空,或当前线程在队列最前面)。只有该条件为 false 时,才尝试 CAS 设置 state 并设置 owner。这意味着即使锁刚被释放,如果队列里已有等待线程,新来的线程也必须入队,不能插队。
- 关键判断:
!hasQueuedPredecessors() && compareAndSetState(0, acquires) -
hasQueuedPredecessors()检查:队列是否非空,且头结点的 next 是否不为 null 且指向当前线程——本质是确认“有没有人比我更早等在队列里” - 效果:吞吐量略低,但避免线程饥饿,响应时间更可预测
非公平锁:允许“插队”,提升吞吐量
非公平锁的 tryAcquire 不检查队列,上来就尝试一次 CAS 获取锁。成功则直接占有;失败才进入 AQS 标准流程(入队 → 挂起 → 等待唤醒)。这就导致刚释放锁的瞬间,可能被一个刚调用 lock() 的新线程抢走,而队列头部的线程还得继续等。
当用户要求为 Java 代码添加注释、修改注释、审查注释、整理注释格式,或要求按照代码注释规范处理 Java 文件时,使用本技能。本技能用于统一生成和维护 Java 类注释模板、按 Git 对比 release 判断版本号、补充每行代码上方中文块注释、调整注释空行规则,并保持 Java 文件 UTF-8 无 BOM 编码。典型触发场景包括:补充类注释、更新 @version、检查 @author 来源、整理行尾注释、把注释放到代码上一行、检查注释是否符合规范、构建前检查 Java 注释风格。
- 典型流程:先 CAS 抢锁 → 失败才调用
acquireQueued(addWaiter(Node.EXCLUSIVE), arg) - 插队行为发生在锁释放后的“窗口期”,不是无序调度,而是利用了 CPU 缓存与 CAS 的快速性
- 效果:减少线程切换和队列操作开销,平均吞吐更高,但可能使等待久的线程延迟增加
AQS 如何统一支撑两种模式
AQS 本身只做三件事:管理 state 状态、维护 FIFO 同步队列、提供 acquire/release 模板方法。公平性由子类(ReentrantLock 的内部 Sync 实现)对 tryAcquire 的定制决定。AQS 的 acquire 方法始终先调用 tryAcquire,再根据返回值决定是否入队——它不干预“要不要查队列”,只执行“查完之后怎么办”。
立即学习“Java免费学习笔记(深入)”;
-
acquire(int arg)流程固定:tryAcquire → 成功则返回;失败则 addWaiter → acquireQueued - 公平 Sync 和非公平 Sync 是 AQS 的两个不同子类,各自重写
tryAcquire,其余逻辑(如release、isHeldExclusively)基本一致 - 队列操作(enq、setHead、shouldParkAfterFailedAcquire 等)对两者完全透明且复用
构造与选择:new ReentrantLock(boolean) 决定行为
ReentrantLock 构造时传入 true 创建 FairSync 实例,传 false(默认)创建 NonfairSync 实例。二者都继承自 AQS,共享同一套 AQS 基础设施,仅在争抢入口逻辑上分叉。
-
new ReentrantLock()→ NonfairSync → 先抢后排队 -
new ReentrantLock(true)→ FairSync → 先看队列再抢 - 注意:公平锁不能完全杜绝插队(比如 Condition.await 后重新 lock 仍要排队),但能保证 lock() 调用的顺序性

















