ReentrantLock 公平锁与非公平锁

发布时间:2026/8/31 21:05:42
ReentrantLock 公平锁与非公平锁 在 Java 显式锁体系中ReentrantLock 相比内置 synchronized 最核心的差异化能力就是支持公平锁与非公平锁双模式切换。两者都是独占可重入锁本质差异只有一个锁被释放时新来的线程是直接插队抢锁还是必须排到等待队列末尾等。很多开发者对两者的认知停留在「公平 排队、非公平 插队」的表层对底层实现、性能差异、选型逻辑、默认设计初衷理解模糊甚至滥用公平锁导致性能瓶颈或者误用非公平锁导致业务饥饿。本文从定义差异、底层实现、优缺点权衡、默认设计逻辑、生产选型指南五个维度讲透两者的本质区别与落地最佳实践。一、核心定义与本质差异两种模式都基于 AQS 独占模式实现都支持可重入特性释放锁唤醒流程都一致核心分歧在于锁竞争的调度策略公平锁严格遵循先来先到所有线程按请求顺序排成 FIFO 队列先到先得绝对不允许插队非公平锁锁释放时新来的线程可以直接尝试抢锁不用先排队抢不到再进入队列尾部允许 “插队”两者没有绝对的优劣之分只是公平性与吞吐量的不同取舍公平锁保证顺序牺牲性能非公平锁牺牲绝对公平换取更高的执行效率。二、公平锁Fair Lock严格排队的先来先得锁2.1 大白话功能与生活化例子公平锁是严格按申请顺序执行的锁。所有线程按照发起加锁请求的先后排成 FIFO 等待队列锁释放时只有队列最前面的线程能被唤醒并拿到锁新来的线程必须排到队列末尾绝对不允许插队抢占。生活化例子银行普通窗口叫号办理业务所有人先取号排队叫到号才能办理哪怕窗口刚好空出来后来的人也必须取号排到最后不能直接上前插队绝对保证先来后到。2.2 底层实现原理公平锁基于 AQS 独占模式实现核心通过队列前置校验保证公平性完整执行逻辑线程调用lock()尝试加锁时首先判断 AQS 的state变量是否为 0锁是否空闲。如果锁空闲先执行hasQueuedPredecessors()校验队列中已有等待线程当前线程不能抢锁必须封装为等待节点排到队列尾部调用LockSupport.park()阻塞等待队列中无等待线程才通过 CAS 原子操作尝试加锁成功则标记当前线程为锁的独占持有者如果锁已被占用当前线程直接封装节点加入队列尾部进入阻塞等待状态。锁释放时仅唤醒队列头部的下一个等待线程按顺序依次拿锁不会批量唤醒、也不会给新来的线程插队机会。核心细节hasQueuedPredecessors()是公平性的核心保障方法只要同步队列里有线程在等待新来的线程就不能抢占锁必须排队从机制上彻底保证先来先得。2.3 核心优缺点优点缺点绝对先来先到无线程饥饿风险所有线程最终都能拿到锁吞吐量低大量线程上下文切换整体性能比非公平锁低 20%~50%执行顺序可预测调度行为固定问题排查简单锁空闲时新来线程也不能用存在资源空转浪费锁利用率低逻辑确定性强适合顺序强一致的业务场景高并发场景下队列积压严重整体处理效率下降明显2.4 选型思考适合对执行顺序有严格要求、低并发、公平性优先级高于性能的场景比如按请求顺序处理的任务队列、公平资源分配、有严格时序要求的状态流转。选型原则只有业务明确要求先来先到、必须保证执行顺序时才选公平锁没有明确顺序要求的场景一律不选公平锁避免不必要的性能损耗。三、非公平锁Nonfair Lock吞吐量优先的插队锁3.1 大白话功能与生活化例子非公平锁是允许插队、效率优先的锁。锁被释放时不管队列里有没有等待的线程新来的线程都可以直接尝试 CAS 抢锁抢到就直接执行业务抢不到再排到队列末尾等待。核心目标是让锁尽量不空闲最大化锁的利用率。生活化例子便利店自助结账机没人结账的时候你过来直接就能用不用看有没有人在后面排队刚好有人结完账你刚好走到就可以直接使用不用严格按先来后到等待。核心是让结账台锁尽量不闲着提升整体效率。3.2 底层实现原理非公平锁是 ReentrantLock 的默认实现同样基于 AQS 独占模式核心逻辑是锁空闲时优先抢锁抢不到再排队线程调用lock()尝试加锁时不做任何队列校验直接用 CAS 尝试把state从 0 修改为 1尝试直接抢占锁。如果 CAS 成功抢到锁直接标记当前线程为锁持有者立刻执行业务逻辑完全不用排队。如果 CAS 失败锁被占用 / 抢锁失败才把当前线程封装成等待节点加入同步队列尾部阻塞等待唤醒。锁释放时唤醒队列头部的等待线程但唤醒过程中如果有新线程过来抢锁新线程有可能先抢到锁被唤醒的线程可能再次抢锁失败继续等待。核心细节非公平锁去掉了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 团队基于工业界大量实践验证的工程权衡核心有三大原因性能优先的通用设计绝大多数业务场景吞吐量、执行效率的优先级远高于绝对公平。非公平锁的吞吐量比公平锁高出 30%~50%性能优势非常明显。默认选择性能更好的方案符合类库设计的「默认最优体验」原则让大多数开发者不用额外配置就能拿到最好的性能。公平锁适用场景极少真实业务中对锁的执行顺序有严格要求的场景非常少绝大多数场景只需要保证线程安全、不出现并发错误不需要严格的先来先到。默认适配绝大多数通用场景减少开发者的配置成本与学习成本。饥饿风险整体可控非公平锁不是完全不公平只是允许新来的线程在锁空闲的瞬间插队。如果抢锁失败线程还是会进入队列排队等待线程最终都会被唤醒不会出现永久饥饿。只是极端高并发下部分线程等待时间变长属于可接受的工程权衡。补充佐证内置锁 synchronized 的底层实现也是非公平调度这也侧面说明通用并发场景下非公平是行业默认的最优选择性能优先级高于绝对公平。六、选型指南与生产避坑6.1 选型口诀无明确顺序要求 → 选默认非公平锁性能优先必须先来先到 → 选公平锁保证执行顺序高并发吞吐量场景 → 优先非公平锁低并发顺序敏感场景 → 选用公平锁6.2 生产避坑指南禁止盲目使用公平锁不要为了 “看起来更合理” 就用公平锁绝大多数业务场景不需要严格顺序平白损失 30% 性能。禁止非公平锁处理顺序敏感业务比如按顺序扣款、按请求优先级处理队列用非公平锁会导致顺序错乱引发业务逻辑异常。重入不改变公平模式同一个线程多次加锁可重入不会破坏公平性也不会切换锁的调度模式两种模式都完全支持重入。注重入多次意味着需要解锁多次否则会导致锁无法释放避免长事务 公平锁组合公平锁本身调度慢再加长事务长时间持有锁会导致队列严重积压吞吐量暴跌。七、总结公平锁与非公平锁没有绝对的好坏只是不同场景下的不同取舍公平锁牺牲性能换顺序确定性非公平锁牺牲绝对公平换高吞吐量。ReentrantLock 默认选择非公平锁是工业界经过大量实践验证的最优选择 —— 通用场景下性能的价值远高于绝对公平。架构选型的核心原则永远是业务需求驱动优先匹配场景而非追求绝对公平或绝对性能。没有最好的锁只有最适配业务的锁。