非公平锁允许新线程在锁空闲(state==0)时直接cas抢占,无需等待队列是否为空;插队仅发生在锁刚释放的瞬时窗口,成功则持有锁,失败才入队;其可重入性与唤醒机制不受影响,而公平锁通过hasqueuedpredecessors()强制fifo排队。

ReentrantLock 的非公平锁允许新线程在等待队列非空时,仍可能绕过队列直接尝试获取锁——这就是所谓“插队”。它不是无条件插队,而是在 lock() 方法中先执行一次 CAS 抢占,成功就直接持有锁,失败才进入排队流程。
非公平锁的插队发生在 lock() 的第一步
非公平锁的 lock() 方法会先调用 nonfairTryAcquire(1),该方法内部直接尝试 CAS 设置 state(锁状态)为 1。只要此时锁未被占用(state == 0),哪怕 AQS 同步队列里已有等待线程,新线程也能抢到锁。
- 插队只发生在“锁刚释放、尚未唤醒后继节点”的极短时间窗口内
- 插队成功不依赖队列是否为空,只取决于当前 state 是否为 0
- 若 CAS 失败(state != 0),才会走 acquire() 流程,加入同步队列排队
插队不会破坏可重入性与锁释放逻辑
即使插队成功,nonfairTryAcquire 仍会检查当前线程是否已持有锁(通过 getExclusiveOwnerThread() == current 判断)。如果是,则仅增加 state 值,保证可重入语义不变。
- 插队线程若已是锁持有者,直接 state += 1,不改变队列结构
- 释放锁(
unlock())始终调用sync.release(1),按 AQS 规则唤醒头节点后继,与是否插队无关 - 插队不会导致死锁或丢失唤醒,因为所有锁变更都基于原子 CAS 和 volatile state
公平锁对比:插队被显式禁止
公平锁的 lock() 方法跳过抢占步骤,直接走 acquire(1),而其 tryAcquire 实现会先检查同步队列是否有前驱节点:hasQueuedPredecessors() 返回 true 就拒绝获取,强制排队。
-
hasQueuedPredecessors()判断的是:队列不为空且头节点的后继不是当前线程 - 这就堵死了“锁一释放、新线程立刻抢到”的路径,确保 FIFO
- 非公平锁省去这个判断,换来更高吞吐,但可能加剧线程饥饿(尤其高竞争下)
实际影响:吞吐优先,但需警惕长尾延迟
非公平锁适合大多数场景,因为减少了线程挂起/唤醒开销。但在持续高竞争且某些线程长期得不到调度时,可能出现个别线程等待时间明显偏长。
- 可通过 JFR 或
getQueueLength()监控队列长度变化趋势 - 若发现某线程反复排队超时,再考虑切换为公平锁(
new ReentrantLock(true)) - 注意:公平锁不能完全杜绝插队——如果持有锁的线程重入,仍是“合法插队”,只是不允许多线程间抢占
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











