高 困难
偏向锁-轻量级锁-重量级锁#
一句话答案#
锁升级路径:无锁→偏向锁(Mark Word 记录线程 ID)→轻量级锁(CAS 竞争)→重量级锁(Monitor 阻塞),不可降级。
核心要点
锁升级过程:
无锁 → 偏向锁 → 轻量级锁 → 重量级锁
(单线程) (短时竞争) (激烈竞争)plaintext| 锁状态 | 适用场景 | 实现方式 | 开销 |
|---|---|---|---|
| 偏向锁 | 只有一个线程访问 | Mark Word 记录线程 ID,后续无需 CAS | 几乎无 |
| 轻量级锁 | 多线程交替执行 | CAS 替换 Mark Word 为栈中 Lock Record | CAS 开销 |
| 重量级锁 | 多线程同时竞争 | Monitor(mutex),未获锁线程阻塞 | 线程阻塞/唤醒 |
升级触发条件:
- 偏向→轻量级:第二个线程尝试获取锁时
- 轻量级→重量级:CAS 抢锁失败(出现真正竞争)即膨胀为重量级锁;自旋(自适应自旋)发生在膨胀后争抢 Monitor 的阶段,「自旋 10 次」是 JDK 6 前
PreBlockSpin的老说法
关键细节:
- JDK15 默认关闭偏向锁(
-XX:-UseBiasedLocking,JEP 374),JDK 18 起偏向锁代码移除、UseBiasedLocking参数失效 - JDK 23 起轻量级锁默认改为新实现(
LockingMode=LM_LIGHTWEIGHT,锁记录放在线程的 lock-stack,Mark Word 不再指向栈上 Lock Record),JDK 26 起旧实现不可再切回;面试讲经典流程时可补这一句 - 锁只能升级不能降级(GC 时特殊情况除外)
- 偏向锁撤销需要等安全点(STW)
面试回答(2分钟版)
synchronized 的锁有一个从低到高的升级过程:无锁、偏向锁、轻量级锁、重量级锁,只能升级不能降级。偏向锁适用于始终只有一个线程访问同步块的场景,JVM 在对象头的 Mark Word 中记录线程 ID,后续该线程再进入时直接比对 ID 即可,几乎没有同步开销。当第二个线程尝试获取锁时,偏向锁撤销升级为轻量级锁,此时线程通过 CAS 将 Mark Word 替换为指向栈中 Lock Record 的指针来竞争锁,CAS 失败说明出现了竞争,就膨胀为重量级锁(膨胀后争抢 Monitor 时会先自适应自旋一会儿再阻塞),底层使用操作系统的 mutex,未获锁的线程直接阻塞挂起,需要内核态切换,开销最大。值得注意的是 JDK 15 默认关闭了偏向锁、JDK 18 连代码都移除了,因为偏向锁撤销需要等待安全点会触发 STW,在现代多线程应用中弊大于利。理解锁升级机制有助于在编码时合理控制锁的粒度和竞争程度。
追问与易错
追问方向:
- “偏向锁撤销的代价?”→ 需要等待全局安全点(STW),暂停持有偏向锁的线程来撤销,在多线程竞争频繁时安全点开销显著
- “JDK15 为什么关闭偏向锁?”→ 现代应用多线程竞争普遍,偏向锁撤销的 STW 开销大于收益;偏向锁维护代码复杂增加了 JVM 维护成本,收益不再明显
- “Mark Word 在不同锁状态存什么?”→ 无锁存 hashcode+age+锁标志 01;偏向锁存线程 ID+epoch+age+标志 01;轻量级锁存指向栈中 Lock Record 的指针+标志 00;重量级锁存指向 Monitor 的指针+标志 10
易错点:
- ❌ 锁能降级——正常情况只升不降
- ❌ 偏向锁就是不加锁——首次偏向有 CAS 设置线程 ID 的开销
- ❌ 轻量级锁会先自旋 10 次再膨胀——HotSpot 中轻量级锁 CAS 失败就膨胀,自旋是在重量级锁(Monitor)阶段做的自适应自旋