公平锁能按线程请求顺序分配锁,实现“先到先得”,需显式启用(new ReentrantLock(true)),基于AQS队列严格FIFO调度,避免饥饿但性能开销较大,吞吐量通常低20%~30%。

Java 中的公平锁能按线程请求锁的顺序分配锁,从而保证“先到先得”的排队执行效果,但需注意:公平锁不是默认行为,必须显式启用,且会带来一定性能开销。
公平锁的核心机制
公平锁基于 同步队列(AQS 的 CLH 队列) 实现。当一个线程尝试获取锁失败时,它会被封装为节点、按 FIFO 顺序插入等待队列尾部;后续释放锁时,只唤醒队列头部的线程,确保严格按申请顺序获得锁。
与非公平锁不同,公平锁在 每次 tryAcquire() 前都会检查队列是否为空、自己是否排在最前面,即使当前锁空闲,也不会插队——这是实现“不抢跑”的关键逻辑。
如何创建并使用公平锁
Java 提供了 ReentrantLock 支持公平模式,通过构造函数参数控制:
立即学习“Java免费学习笔记(深入)”;
-
启用公平锁:
new ReentrantLock(true) -
禁用公平锁(默认):
new ReentrantLock()或new ReentrantLock(false)
示例代码:
ReentrantLock fairLock = new ReentrantLock(true); // 公平锁
List<Thread> threads = new ArrayList<>();
for (int i = 0; i < 5; i++) {
final int id = i;
Thread t = new Thread(() -> {
System.out.println("线程 " + id + " 尝试获取锁");
fairLock.lock(); // 阻塞直到轮到自己
try {
System.out.println("线程 " + id + " 执行中...");
Thread.sleep(100);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
System.out.println("线程 " + id + " 释放锁");
fairLock.unlock();
}
});
threads.add(t);
t.start();
}
运行时通常看到输出顺序接近 0→1→2→3→4(取决于线程启动和调度细微差异,但排队逻辑由锁保障)。
公平锁的注意事项
- 不能完全消除调度不确定性:JVM 线程调度仍可能造成微小延迟,公平锁保证的是“逻辑排队顺序”,而非绝对精确的时间顺序
- 性能代价明显:每次 lock() 都需检查队列状态、插入节点、CAS 更新 tail,吞吐量通常比非公平锁低 20%~30%
- 仅对同一把锁有效:多个公平锁之间互不影响;若业务涉及多把锁或混合锁类型,整体顺序无法全局保证
- 可重入性依然保留:同一线程重复加锁不会被阻塞,也不影响其他线程的排队位置
什么场景适合用公平锁?
- 对响应时间波动敏感,要求各线程等待时间尽可能均衡(如实时任务分发)
- 存在明显长持有锁 + 短持有锁混合的情况,避免短任务被持续饿死
- 调试或验证并发逻辑时,需要确定性行为辅助分析
日常高吞吐场景(如缓存更新、日志写入)更推荐默认的非公平锁,除非实测证明公平性带来的稳定性收益超过性能损失。


















