news 2026/9/5 23:59:19

ReentrantLock 公平锁与非公平锁

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
ReentrantLock 公平锁与非公平锁

在 Java 显式锁体系中,ReentrantLock 相比内置 synchronized 最核心的差异化能力,就是支持公平锁与非公平锁双模式切换。两者都是独占可重入锁,本质差异只有一个:锁被释放时,新来的线程是直接插队抢锁,还是必须排到等待队列末尾等

很多开发者对两者的认知停留在「公平 = 排队、非公平 = 插队」的表层,对底层实现、性能差异、选型逻辑、默认设计初衷理解模糊,甚至滥用公平锁导致性能瓶颈,或者误用非公平锁导致业务饥饿。本文从定义差异、底层实现、优缺点权衡、默认设计逻辑、生产选型指南五个维度,讲透两者的本质区别与落地最佳实践。

一、核心定义与本质差异

两种模式都基于 AQS 独占模式实现,都支持可重入特性,释放锁,唤醒流程都一致,核心分歧在于锁竞争的调度策略

  • 公平锁:严格遵循先来先到,所有线程按请求顺序排成 FIFO 队列,先到先得,绝对不允许插队

  • 非公平锁:锁释放时,新来的线程可以直接尝试抢锁,不用先排队,抢不到再进入队列尾部,允许 “插队”

两者没有绝对的优劣之分,只是公平性与吞吐量的不同取舍:公平锁保证顺序牺牲性能,非公平锁牺牲绝对公平换取更高的执行效率。

二、公平锁(Fair Lock):严格排队的先来先得锁

2.1 大白话功能与生活化例子

公平锁是严格按申请顺序执行的锁。所有线程按照发起加锁请求的先后,排成 FIFO 等待队列;锁释放时,只有队列最前面的线程能被唤醒并拿到锁,新来的线程必须排到队列末尾,绝对不允许插队抢占。

生活化例子:银行普通窗口叫号办理业务,所有人先取号排队,叫到号才能办理;哪怕窗口刚好空出来,后来的人也必须取号排到最后,不能直接上前插队,绝对保证先来后到。

2.2 底层实现原理

公平锁基于 AQS 独占模式实现,核心通过队列前置校验保证公平性,完整执行逻辑:

  1. 线程调用lock()尝试加锁时,首先判断 AQS 的state变量是否为 0(锁是否空闲)。

  2. 如果锁空闲,先执行hasQueuedPredecessors()校验

    1. 队列中已有等待线程:当前线程不能抢锁,必须封装为等待节点排到队列尾部,调用LockSupport.park()阻塞等待

    2. 队列中无等待线程:才通过 CAS 原子操作尝试加锁,成功则标记当前线程为锁的独占持有者

  3. 如果锁已被占用,当前线程直接封装节点加入队列尾部,进入阻塞等待状态。

  4. 锁释放时,仅唤醒队列头部的下一个等待线程,按顺序依次拿锁,不会批量唤醒、也不会给新来的线程插队机会。

核心细节:hasQueuedPredecessors()是公平性的核心保障方法,只要同步队列里有线程在等待,新来的线程就不能抢占锁,必须排队,从机制上彻底保证先来先得。

2.3 核心优缺点

优点

缺点

绝对先来先到,无线程饥饿风险,所有线程最终都能拿到锁

吞吐量低,大量线程上下文切换,整体性能比非公平锁低 20%~50%

执行顺序可预测,调度行为固定,问题排查简单

锁空闲时新来线程也不能用,存在资源空转浪费,锁利用率低

逻辑确定性强,适合顺序强一致的业务场景

高并发场景下队列积压严重,整体处理效率下降明显

2.4 选型思考

适合对执行顺序有严格要求、低并发、公平性优先级高于性能的场景,比如按请求顺序处理的任务队列、公平资源分配、有严格时序要求的状态流转。

选型原则:只有业务明确要求先来先到、必须保证执行顺序时,才选公平锁;没有明确顺序要求的场景,一律不选公平锁,避免不必要的性能损耗。

三、非公平锁(Nonfair Lock):吞吐量优先的插队锁

3.1 大白话功能与生活化例子

非公平锁是允许插队、效率优先的锁。锁被释放时,不管队列里有没有等待的线程,新来的线程都可以直接尝试 CAS 抢锁,抢到就直接执行业务,抢不到再排到队列末尾等待。核心目标是让锁尽量不空闲,最大化锁的利用率。

生活化例子:便利店自助结账机,没人结账的时候你过来直接就能用,不用看有没有人在后面排队;刚好有人结完账,你刚好走到就可以直接使用,不用严格按先来后到等待。核心是让结账台(锁)尽量不闲着,提升整体效率。

3.2 底层实现原理

非公平锁是 ReentrantLock 的默认实现,同样基于 AQS 独占模式,核心逻辑是锁空闲时优先抢锁,抢不到再排队

  1. 线程调用lock()尝试加锁时,不做任何队列校验,直接用 CAS 尝试把state从 0 修改为 1,尝试直接抢占锁。

  2. 如果 CAS 成功(抢到锁),直接标记当前线程为锁持有者,立刻执行业务逻辑,完全不用排队。

  3. 如果 CAS 失败(锁被占用 / 抢锁失败),才把当前线程封装成等待节点,加入同步队列尾部,阻塞等待唤醒。

  4. 锁释放时,唤醒队列头部的等待线程;但唤醒过程中如果有新线程过来抢锁,新线程有可能先抢到锁,被唤醒的线程可能再次抢锁失败,继续等待。

核心细节:非公平锁去掉了hasQueuedPredecessors()前置校验,这是和公平锁最核心的代码差异。虽然允许插队,但 AQS 队列里的等待线程最终还是会被唤醒,不会永久饥饿,只是可能被新来的线程多次插队,等待时间变长。

3.3 核心优缺点

优点

缺点

吞吐量高,锁利用率高,减少线程上下文切换,整体性能优于公平锁

存在线程饥饿风险,高并发下部分线程可能长时间抢不到锁

锁空闲时立刻能被使用,无资源空转浪费,执行效率高

执行顺序不可预测,调度不确定性强,不适合顺序敏感业务

高并发场景下整体处理能力强,适配绝大多数通用业务

偶发问题排查难度高,执行顺序不固定,问题难复现

3.4 选型思考

适合高并发、吞吐量优先、无严格顺序要求的通用业务场景,比如普通数据更新、资源独占加锁、接口并发控制,这是绝大多数业务场景的选择。

选型原则:没有明确公平性要求的场景,默认选非公平锁,用少量的公平性不确定性换取大幅的性能提升,投入产出比最高。

四、核心差异对照表

对比维度

公平锁

非公平锁

调度核心原则

严格先来先到,FIFO 队列顺序执行

允许插队,锁空闲时新来线程可直接抢占

核心实现逻辑

hasQueuedPredecessors()队列前置校验

无队列校验,锁空闲直接 CAS 抢锁

是否为默认模式

❌ 不是默认

✅ 官方默认模式

整体吞吐量

低,线程切换与队列开销大

高,减少空转与上下文切换损耗

线程饥饿风险

无,绝对先来先得

有,高并发下部分线程等待时间变长

执行顺序特性

可预测、固定顺序

不可预测、调度灵活

性能损耗

高,大量阻塞唤醒切换

低,锁利用率最大化

核心适用场景

顺序敏感、低并发、公平优先

高并发、吞吐量优先、无顺序要求

五、默认模式是什么?为什么这么设计?

5.1 默认模式结论

ReentrantLock默认是非公平锁。无参构造方法直接创建的就是非公平锁,只有传入true参数时才创建公平锁。

// 默认创建非公平锁 ReentrantLock lock = new ReentrantLock(); // 手动指定创建公平锁 ReentrantLock fairLock = new ReentrantLock(true);

5.2 为什么默认选择非公平锁?(架构师设计思考)

这不是随意的设计,而是 JDK 团队基于工业界大量实践验证的工程权衡,核心有三大原因:

  1. 性能优先的通用设计绝大多数业务场景,吞吐量、执行效率的优先级远高于绝对公平。非公平锁的吞吐量比公平锁高出 30%~50%,性能优势非常明显。默认选择性能更好的方案,符合类库设计的「默认最优体验」原则,让大多数开发者不用额外配置就能拿到最好的性能。

  2. 公平锁适用场景极少真实业务中,对锁的执行顺序有严格要求的场景非常少,绝大多数场景只需要保证线程安全、不出现并发错误,不需要严格的先来先到。默认适配绝大多数通用场景,减少开发者的配置成本与学习成本。

  3. 饥饿风险整体可控非公平锁不是完全不公平,只是允许新来的线程在锁空闲的瞬间插队。如果抢锁失败,线程还是会进入队列排队,等待线程最终都会被唤醒,不会出现永久饥饿。只是极端高并发下部分线程等待时间变长,属于可接受的工程权衡。

补充佐证:内置锁 synchronized 的底层实现也是非公平调度,这也侧面说明:通用并发场景下,非公平是行业默认的最优选择,性能优先级高于绝对公平。

六、选型指南与生产避坑

6.1 选型口诀

  • 无明确顺序要求 → 选默认非公平锁,性能优先

  • 必须先来先到 → 选公平锁,保证执行顺序

  • 高并发吞吐量场景 → 优先非公平锁

  • 低并发顺序敏感场景 → 选用公平锁

6.2 生产避坑指南

  • 禁止盲目使用公平锁:不要为了 “看起来更合理” 就用公平锁,绝大多数业务场景不需要严格顺序,平白损失 30%+ 性能。

  • 禁止非公平锁处理顺序敏感业务:比如按顺序扣款、按请求优先级处理队列,用非公平锁会导致顺序错乱,引发业务逻辑异常。

  • 重入不改变公平模式:同一个线程多次加锁(可重入),不会破坏公平性,也不会切换锁的调度模式,两种模式都完全支持重入。(注:重入多次意味着需要解锁多次,否则会导致锁无法释放

  • 避免长事务 + 公平锁组合:公平锁本身调度慢,再加长事务长时间持有锁,会导致队列严重积压,吞吐量暴跌。

七、总结

公平锁与非公平锁没有绝对的好坏,只是不同场景下的不同取舍:公平锁牺牲性能换顺序确定性,非公平锁牺牲绝对公平换高吞吐量。

ReentrantLock 默认选择非公平锁,是工业界经过大量实践验证的最优选择 —— 通用场景下,性能的价值远高于绝对公平。架构选型的核心原则永远是:业务需求驱动,优先匹配场景,而非追求绝对公平或绝对性能。没有最好的锁,只有最适配业务的锁。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/4 18:24:32

用Qt Creator打造自己的串口调试助手:QSerialPort实战与踩坑全记录

简介:基于Qt Creator的Serial Port串口调试助手项目代码,面向需要快速搭建串口通信调试工具或学习Qt SerialPort与实时数据可视化的开发者。项目不仅实现常规串口数据收发,还集成波形显示功能,模拟VOFA上位机Plot效果,…

作者头像 李华
网站建设 2026/9/4 13:49:55

从零构建一个可靠的嵌入式 C 语言 FIFO 缓冲模块

在嵌入式开发中,很多问题表面上是“数据处理不过来”,本质上却是数据生产速度与数据消费速度不匹配。 例如: UART 中断不断接收数据,而主循环还来不及解析; DMA 突然完成一批数据传输,需要等待后续任务处理; 传感器持续采样,而算法模块只能周期性读取; 通信协议存在突…

作者头像 李华
网站建设 2026/9/2 23:53:36

公司到底需不需要呼叫系统?——从客户接待、专业形象到成本真相

前言很多中小企业的老板都会问一个问题:我们公司现在电话不多,有必要上一套呼叫系统吗?这个问题背后其实藏着几个更具体的困惑:客户来电怎么接才算专业?公司名片上印什么号码显得正规?员工离职了客户打他手…

作者头像 李华
网站建设 2026/9/2 14:52:54

基于OpenCV的象棋识别与棋谱定位:传统图像处理实战

简介:本资源是一套基于OpenCV的象棋图像识别与棋谱定位完整实现方案,面向人工智能课程设计、本科毕设及CV方向初学者,解决传统棋类图像中棋子分类识别与坐标精确定位两大核心问题。压缩包共394个文件,含385张标注清晰的棋子PNG样本…

作者头像 李华
网站建设 2026/9/5 15:10:06

MATLAB实现EKF电池SOC估计:从建模到仿真完整流程

简介:本资源是一套面向电池管理系统(BMS)算法工程师、新能源方向研究生及MATLAB仿真学习者的SOC估计算法实践材料,聚焦锂电池非线性建模与状态估计核心问题,提供基于扩展卡尔曼滤波(EKF)的完整S…

作者头像 李华