作为Java并发编程中最常用的显式锁,ReentrantLock的公平锁与非公平锁是大厂面试的高频考点,更是性能优化的关键开关——据鳄鱼java2025年并发课程数据统计,高并发场景下误用公平锁,会导致系统吞吐量暴跌70%;而正确切换为非公平锁,可将吞吐量提升300%以上。掌握ReentrantLock公平锁与非公平锁的差异,不仅能帮你通过面试,更能在生产环境中平衡并发性能与线程公平性。
一、ReentrantLock是什么?synchronized的“进阶替代方案”

ReentrantLock是Java.util.concurrent.locks包下的显式锁实现,相比synchronized,它具有三大核心优势:1. 支持可重入(同一线程可多次获取同一锁,不会死锁);2. 支持中断(线程在等待锁时可被中断,避免永久阻塞);3. 支持超时获取锁(线程在指定时间内未获取到锁则返回,防止无限等待)。而公平锁与非公平锁是ReentrantLock的核心配置选项,直接决定了锁的获取策略与性能表现。鳄鱼java学员在电商秒杀系统中,曾用ReentrantLock替代synchronized,将接口吞吐量提升了200%,其中关键就是选择了非公平锁。
二、ReentrantLock公平锁与非公平锁:核心定义与直观差异
公平锁与非公平锁的核心差异,在于线程获取锁的顺序: - 公平锁:严格遵循“先到先得”的队列顺序,线程会排队等待锁,当前线程释放锁后,只有队列中的第一个线程能获取锁,不会出现“插队”现象; - 非公平锁:线程不会严格排队,会先尝试直接抢占锁,如果抢占失败,再进入队列等待。这意味着后请求锁的线程可能先获取到锁,出现“插队”。
用直观的生活案例类比:公平锁像医院挂号,必须按排队顺序叫号;非公平锁像地铁站检票,有些人会直接冲到闸机口,能抢过就先过,抢不过再排队。鳄鱼java导师会反复强调:这种“插队”机制是性能差异的核心来源。
三、底层源码拆解:公平锁与非公平锁的实现逻辑
ReentrantLock的底层基于AQS(AbstractQueuedSynchronizer,抽象队列同步器)实现,公平锁与非公平锁的差异主要体现在lock()方法的实现上。
**公平锁的lock方法源码**(关键在于hasQueuedPredecessors()判断):
static final class FairSync extends Sync {
final void lock() {
acquire(1);
}
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
// 核心:判断队列是否有前驱节点,没有才尝试CAS抢锁
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
// 可重入逻辑:当前线程已持有锁,state计数+1
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
}
公平锁在尝试获取锁时,会先调用hasQueuedPredecessors()判断AQS队列中是否有等待的线程,如果有,则不会抢锁,直接进入队列排队,确保严格的顺序性。
**非公平锁的lock方法源码**(直接尝试CAS抢锁):
static final class NonfairSync extends Sync {
final void lock() {
// 核心:直接尝试CAS抢锁,成功则获取锁
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
}
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
// 没有hasQueuedPredecessors判断,直接抢锁
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
// 可重入逻辑与公平锁一致
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
非公平锁在lock()方法中直接调用compareAndSetState()尝试抢占锁,如果成功则直接获取锁,无需判断队列,这就是“插队”的底层实现。只有抢锁失败时,才会进入队列等待,和公平锁逻辑一致。
四、性能实测:公平锁真的比非公平锁慢3倍?
鳄鱼java实验室在8核16G服务器上进行了性能测试,模拟10万次并发加锁/解锁操作,结果如下:
- 公平锁:吞吐量1210 TPS,平均响应时间826 us,线程平均等待时间12 ms;
- 非公平锁:吞吐量4530 TPS,平均响应时间221 us,线程平均等待时间2.8 ms。
实测数据显示,非公平锁的吞吐量是公平锁的3.75倍,响应时间仅为公平锁的1/4。原因在于公平锁需要维护队列的严格顺序,每次加锁都要判断队列,而非公平锁减少了队列操作的开销,且能避免线程上下文切换(抢占锁成功的线程无需进入队列,直接执行)。
但公平锁并非毫无优势:在严格要求顺序的场景下,比如金融交易系统的转账排队、医院挂号系统,公平锁能保证线程不会饥饿(不会有线程永远抢不到锁),而非公平锁可能导致某些线程长期无法获取锁,出现“饥饿”现象。
五、实战选型指南:什么时候用公平锁,什么时候用非公平锁?
鳄鱼java导师总结了两种锁的选型场景,帮你精准决策:
**选择非公平锁的场景(90%的业务场景)**: 1. 高并发场景:比如电商秒杀、直播礼物发送、接口限流,追求高吞吐量和低延迟; 2. 锁持有时间短:比如简单的原子操作、缓存更新,非公平锁的抢占优势更明显; 3. 对线程饥饿不敏感:比如用户请求处理,即使个别线程等待久一点,也不会影响核心业务。
**选择公平锁的场景(10%的特殊场景)**: 1. 严格要求顺序的业务:比如金融转账、排队系统、股票交易,必须保证线程按顺序执行; 2. 线程持有锁时间长:比如大数据计算、文件批量处理,公平锁能避免个别线程长期饥饿; 3. 对公平性要求极高:比如政府政务系统、医疗挂号系统,公平性优先于性能。
总结与思考
**ReentrantLock公平锁与非公平锁**的选择,本质是“性能”与“公平性”的权衡。在多数高并发场景下,非公平锁的性能优势远大于公平性的损失,是默认的最优选择;但在对顺序、公平性要求极高的场景,公平锁是保障业务正确性的唯一选择。鳄鱼java学员在金融交易系统中,曾因误用非公平锁导致转账顺序错乱,后来切换为公平锁,彻底解决了业务问题,但同时也通过优化锁粒度、减少锁持有时间,弥补了公平锁的性能损失。
你在项目中遇到过因锁选型错误导致的性能或业务问题吗?如何平衡性能与公平性?欢迎在鳄鱼java社区分享你的经验与思考,我们一起探讨并发编程的
版权声明
本文仅代表作者观点,不代表百度立场。
本文系作者授权百度百家发表,未经许可,不得转载。





