AQS 相关面试追问,经常不问"原理",而问"取舍":
- 为什么默认非公平?
- 什么场景要公平?
- 自旋什么时候是赚的,什么时候是亏的?
这篇把这些问题讲透。
你必须记住的 3 句话(面试直出):
- 公平/非公平的本质是"能不能插队走快路径",不是"有没有队列"。
- 自旋的收益来自"避免 park/unpark 的调度成本",但临界区一长就会变成 CPU 空转。
- 线上锁问题先看现象:
parking多=排队等待;CPU 高但吞吐不涨=可能 CAS 热点 + 自旋。
0. 快速选型表(背下来就够用)
| 目标/约束 | 更推荐 | 理由 |
|---|---|---|
| 吞吐优先(大多数业务) | 非公平锁 | 快路径更容易命中,减少排队与调度开销 |
| 等待顺序可预测/更强公平诉求 | 公平锁 | 禁止插队,减少短期饥饿 |
| 临界区很短(纯内存) | 少量自旋可接受 | 可能比阻塞/唤醒更划算 |
| 临界区包含 IO/慢 SQL/RPC | 避免自旋(让线程阻塞) | 自旋会放大 CPU 浪费与抖动 |
1. 公平 vs 非公平:差的不是"有没有队列",而是"能不能插队"
1.1 公平锁(Fair)
核心规则:
- 只要队列里有人排在你前面,你就不允许直接抢
收益:
- 更可预测,减少饥饿
代价:
- 更频繁的排队/唤醒,吞吐更低
1.2 非公平锁(Nonfair)
核心规则:
- 新来的线程可以先 CAS 抢一次 state(插队)
- 失败再入队
一个面试加分点:
- 在
ReentrantLock中,非公平实现会先做一次compareAndSetState(0, 1)的快路径;只有失败才走 AQS 入队逻辑。
收益:
- 快路径命中率更高,吞吐通常更高
代价:
- 短期不公平,极端情况下可能出现"某些线程总抢不到"(但一般会靠调度逐步缓解)
关于"饥饿"的边界你可以这么答:
- 非公平锁允许插队,所以"理论上"可能饥饿;但 JVM/OS 调度通常会让等待线程获得执行机会,实践中更多是"短期不公平"。
2. 自旋 vs 阻塞:AQS 的策略是"先试、再睡"
AQS 的典型行为可以概括为:
- 快速 CAS 尝试获取(快路径)
- 失败 -> 入队
- 入队后会在合适时机再尝试一次(通常是成为 head 后继时)
- 再失败 ->
park阻塞
为什么这么设计:
- 阻塞/唤醒是系统调用级别的成本(切换、调度、缓存失效)
- 如果锁很快释放,少量自旋可能更划算
3. 怎么判断"自旋赚不赚"
经验判断:
-
临界区很短(几十纳秒~微秒级,纯内存操作)
- 适合少量自旋(避免 park/unpark)
-
临界区较长(IO、RPC、慢 SQL、日志同步写、磁盘)
- 自旋大概率亏:CPU 空转 + 还会放大系统抖动
你可以用一句话回答:
- 自旋适合"短临界区 + 低竞争",否则应该让线程阻塞,把 CPU 让给能干活的线程。
线上识别自旋/竞争过热的常见信号:
- CPU 占用高、上下文切换也高,但 QPS/TPS 不涨
- 大量线程反复出现在 acquire 相关栈(且业务线程没在做 IO)
4. 常见坑:公平/非公平 + 自旋在业务里怎么出事故
-
坑 1:把慢操作放进锁里
- 例如锁内调用 RPC/写磁盘/慢 SQL
- 结果:锁队列堆积,线程大量
parking,RT 飙升
-
坑 2:以为公平锁能解决一切
- 公平只是减少插队,不会让临界区变短
- 临界区长时,公平锁反而更"平均地慢"
-
坑 3:高并发下用一个全局锁保护大对象
- 结果:吞吐被锁串行化
- 优先考虑:拆分锁粒度、分段、无锁/原子类、读写分离等
5. 线上排查清单(非常实用)
当你怀疑锁竞争时:
- 先看指标:RT、QPS、线程数、CPU、GC
- jstack 抓 3 次(间隔几秒)
- 大量线程在
AbstractQueuedSynchronizer.acquire/LockSupport.park - 且卡在同一把锁的获取路径
- 大量线程在
- 结合代码定位锁范围:
- 锁内是否包含 IO/慢 SQL/外部调用
你还可以补一个更"面试像实战"的说法:
- 先定位"谁在持有锁":抓 3 次 jstack,找出持锁线程的业务栈,看它在锁内到底干了什么。
定位到后常见修复手段:
- 缩小锁范围
- 拆分锁(按 key 分段)
- 读多写少用读写锁或 CopyOnWrite(谨慎)
- 计数聚合用
LongAdder替代AtomicLong(热点写)
6. 自测清单(你要能顺口讲出来)
-
Q:为什么
ReentrantLock默认非公平?- A:允许插队提升快路径命中率与吞吐,一般业务更看重性能。
-
Q:什么时候选公平锁?
- A:需要更可预测的等待顺序、避免某些线程长时间拿不到锁(例如特定调度场景、资源分配更敏感)。
-
Q:自旋一定浪费 CPU 吗?
- A:不一定。临界区很短且竞争不激烈时,自旋减少 park/unpark 成本反而更快;但竞争激烈/临界区长会让 CPU 空转。
-
Q:公平锁是不是"严格 FIFO"?
- A:它的目标是"减少插队",不保证严格 FIFO 获得锁;被唤醒后仍要靠 CAS 再竞争。
7. 30 秒背诵稿
公平与非公平的核心差异是"能否插队走快路径":公平锁发现队列有前驱就排队,非公平锁先 CAS 抢一次提高吞吐。AQS 获取失败后入队,head 后继会再竞争,仍失败才 park 阻塞;自旋适合短临界区、低竞争以减少 park/unpark 成本,但临界区包含 IO/慢 SQL/RPC 时会造成 CPU 空转与延迟抖动。