← 每日面试题 学习看板

公平锁和非公平锁的区别?

面试题/00java/02Java 并发编程/06线程同步与锁机制/公平锁和非公平锁的区别?.md

面试记录 2026-10-20

题目:公平锁和非公平锁的区别?

难度:low

标签:java/lock, java/concurrency

评分:⭐⭐☆☆☆(2/5)

我的回答

公平锁和非公平锁是依据锁释放后唤醒阻塞中线程具体谁来执行进行划分的;

按照哪个线程等待时间长哪个线程执行就是公平锁,也就是给线程按时间排序,先来后到;

非公平锁是唤醒所有等待线程,大家争抢,谁抢到了谁就执行,看的是线程调度优先级;

java中synchronized升级为重量级锁后是非公平锁,使用aqs的reechangelock可以自己选择公平锁或者非公平锁,在创建锁的时候可以设置;

非公平锁可以让更快的线程执行,但是可能会有一些线程一直执行不到的情况;

点评

优点:抓住"先来后到 vs 争抢"的本质差异;知道 ReentrantLock 构造器可切公平性;知道非公平会饥饿

不足:① "非公平锁唤醒所有等待线程"是硬伤——释放锁只 unpark 后继一个节点,非公平指的是新来的线程可以插队(barging),不是惊群;② 公平锁判据不是"等待时间",而是 hasQueuedPredecessors()——队列里有没有前驱;③ 说"看线程调度优先级"错,OS 优先级与锁竞争无关;④ 漏了公平锁吞吐更低、非公平默认(ReentrantLock 默认 false)

补充:公平锁 = 严格 FIFO + 无插队;非公平锁 = 允许新线程直接 CAS 抢,抢不到再入队;差距在高竞争时才明显(吞吐差 10%~100%)

完整讲解

第一部分:知识重点剖析

核心考点定位:能否说清"公平"的判据是队列前驱判定,而非等待时长;非公平的收益与代价。

知识点拆解:

  1. 公平锁判据:FairSync.tryAcquire 先调 hasQueuedPredecessors()——队列中是否存在排在当前线程之前的节点;有则入队,无则 CAS 抢
  2. 非公平锁判据:NonfairSync.tryAcquire 直接 compareAndSetState(0, 1),成功即插队;失败才走 acquireQueued 入队阻塞
  3. 唤醒机制:释放时 unparkSuccessor(h) 只唤醒一个后继,不是全部(惊群在 AQS 里被刻意避免)
  4. 代价权衡:非公平减少线程唤醒的上下文切换,吞吐更高;公平保证 FIFO,避免长尾饥饿,但每次都要 CAS 检查队列
  5. synchronized 的公平性:偏向锁/轻量级锁阶段本质是 CAS 抢占,重量级锁的 Monitor 不保证 FIFO,整体非公平
按「底层原理 → 设计权衡 → 工程实践」递进排列。

第二部分:考察方向与延伸递进

考察方向:

延伸递进面试题:

  1. 非公平锁为什么吞吐更高?
  1. 公平锁能完全避免饥饿吗?
  1. 读写锁的公平模式有什么特殊问题?

第三部分:面试者参考回答(三步法)

【一句话结论】

面试官您好,公平与非公平的差别就一句话:抢锁前要不要先看队列里有没有人在排队。

【三点展开】

第一,公平锁的 tryAcquire 会先调 hasQueuedPredecessors(),只要前面还有人排队就老老实实入队,严格 FIFO,不会插队。

第二,非公平锁直接 CAS 把 state 从 0 改成 1,抢到就跑,抢不到才入队——所以"非公平"说的是允许新线程插队,不是唤醒所有线程一起抢,释放时其实只唤醒一个后继节点。

第三,这个插队是有意为之:刚释放锁的线程还在 CPU 上、缓存是热的,让它继续跑能省掉一次上下文切换,所以非公平吞吐更高;代价是极端情况下远端线程可能一直抢不到,产生饥饿。

【一句边界】

当然,如果业务对顺序敏感,比如库存扣减、订单状态机,就该用 new ReentrantLock(true);普通缓存、计数器场景没必要为公平付吞吐代价,默认非公平就好。