Java 锁升级:偏向锁、轻量级锁、重量级锁是怎么回事?

#Java#面试八股

Java 锁升级:偏向锁、轻量级锁、重量级锁是怎么回事?

synchronized 在 HotSpot 里不是"一上来就上重量级锁",而是按竞争程度逐级升级, 因为线程阻塞/唤醒要陷入内核,代价远高于用户态 CAS。

1. 对象头里的锁状态

对象头 Mark Word 用 2 bit 表示锁状态:

状态Mark Word 内容开销
无锁hash + 分代年龄(01)—
偏向锁线程 ID + epoch(01,偏向位=1)一次 CAS(首次)
轻量级锁指向栈中锁记录的指针(00)每次 CAS
重量级锁指向 ObjectMonitor 的指针(10)内核态阻塞/唤醒

2. 升级路径

无锁 → 偏向锁(同一线程反复进入) → 轻量级锁(出现第二个线程竞争) → 重量级锁(自旋失败/竞争激烈)
  • 偏向锁:假设"永远只有一个线程用这把锁",第一次 CAS 把线程 ID 写进 Mark Word,之后进同步块 只需比较 ID,几乎零开销。出现第二个线程竞争时撤销(撤销要 STW,代价不小)。
  • 轻量级锁:在栈帧里建 Lock Record,用 CAS 把 Mark Word 指向它;失败则自旋重试。
  • 重量级锁:自旋到阈值仍失败,膨胀成 ObjectMonitor,线程进入等待队列(内核态挂起)。

3. 几个常被追问的点

Q:锁能降级吗? HotSpot 基本不会主动降级(早期有,后来去掉)。所以"短暂竞争导致膨胀"后, 即使之后没竞争,锁也一直是重量级——这就是"不要在高并发路径上无脑 synchronized"的原因之一。

Q:偏向锁为什么被废弃? JDK 15 起默认关闭、JDK 18 移除(JEP 374)。原因是偏向锁的撤销需要 STW, 在"大量短生命周期对象 + 多线程"的场景里,撤销成本高于收益。

Q:自旋多少圈? 自适应自旋:由上一次在该锁上的自旋成功率动态决定,不是固定值。

4. 实践建议

  1. 锁粒度尽量小:只包住临界区,别把 IO 放进同步块;
  2. 能用 ReentrantLock 的 tryLock(timeout) 就别死等,避免线程无限挂起;
  3. 读多写少用 ReadWriteLock / StampedLock(后者乐观读,适合读极多场景);
  4. 别锁字符串常量/包装类(Integer 缓存 -128~127,不同业务可能锁到同一个对象)。

小结

锁升级本质是"用空间与复杂度换性能":偏向锁省 CAS、轻量级锁省内核切换、重量级锁保证公平与吞吐。 面试答这题的关键不是背四级状态,而是说清为什么要分级——阻塞线程的代价太高, 所以 JVM 先用用户态手段(偏向/CAS/自旋)扛,扛不住才陷入内核。

笔记加载中…