公平锁与非公平锁的核心区别在于是否在获取锁前检查队列:非公平锁直接cas抢锁,成功即持锁;公平锁必须先调用hasqueuedpredecessors()确认无前序等待者才允许抢锁,由此导致线程调度、内存访问和上下文切换的显著差异。

公平锁和非公平锁的核心区别,不在“是否排队”,而在于“要不要先看队列再抢锁”——这个动作看似微小,却直接决定了线程调度路径、内存访问模式和上下文切换频率。
抢占时机:一上来就抢 vs 必须等轮到自己
非公平锁(NonfairSync)的 lock() 方法第一步就是 CAS 尝试获取锁:
- 只要
state == 0,就立刻执行compareAndSetState(0, 1),成功即持锁,不查队列、不入队、不挂起; - 失败后才走标准 AQS 流程:封装节点 → 入队尾 → park 等待唤醒。
公平锁(FairSync)则严格多一步前置检查:
- 即使
state == 0,也必须先调用hasQueuedPredecessors(); - 该方法判断当前线程是否排在等待队列最前面(即 head.next 是否为当前节点);
- 只有返回
false(无人排队或自己是队首)时,才允许后续 CAS。
变量访问开销:一次写 vs 多次 volatile 读
非公平锁在快路径中只做一次 volatile 写(CAS 修改 state),完全避开队列结构访问;
公平锁的 hasQueuedPredecessors() 则带来额外负担:
- 至少两次 volatile 读:读
state和读head节点; - 多核环境下频繁读同一 volatile 地址,触发缓存行失效(cache line invalidation),加剧总线争用;
- 即使队列为空,也要完整遍历链表逻辑,无法跳过。
线程状态变化:用户态直通 vs 内核态挂起
非公平锁在锁释放瞬间,常被新线程直接抢中——全程用户态、无阻塞、无调度介入;
公平锁则强制所有新请求线程经历完整等待流程:
- 新建 Node 实例;
- CAS 更新 tail 指针;
- 设置前驱 waitStatus = SIGNAL;
- 调用
LockSupport.park()进入 WAITING 状态。
这些步骤带来微秒至毫秒级延迟,包含上下文切换、内存分配与 GC 压力。
实际行为:“能抢则抢”不是“永远插队”
非公平锁并非无视秩序:
- 抢失败后,仍乖乖入队、按 FIFO 被唤醒;
- 饥饿仅出现在极端场景:临界区极短 + 新线程持续高频抵达;
- 正常业务中,只要临界区超过几十纳秒,队列中的线程基本都能获得执行机会。
公平锁的“绝对公平”代价是:每个线程无论能否立即执行,都必须走一遍 park/unpark,把本可并行的锁获取动作序列化。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











