面试知识库
高 困难

偏向锁-轻量级锁-重量级锁#

一句话答案#

锁升级路径:无锁→偏向锁(Mark Word 记录线程 ID)→轻量级锁(CAS 竞争)→重量级锁(Monitor 阻塞),不可降级。

核心要点

锁升级过程:

无锁 → 偏向锁 → 轻量级锁 → 重量级锁
      (单线程)  (短时竞争)   (激烈竞争)
plaintext
锁状态适用场景实现方式开销
偏向锁只有一个线程访问Mark Word 记录线程 ID,后续无需 CAS几乎无
轻量级锁多线程交替执行CAS 替换 Mark Word 为栈中 Lock RecordCAS 开销
重量级锁多线程同时竞争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)阶段做的自适应自旋