synchronized 锁升级的过程

在 Java 中,锁升级特指synchronized在 JDK 1.6 后引入的优化机制:锁状态从无锁逐步升级为偏向锁、轻量级锁、重量级锁,且升级方向是单向的(不可降级)。整个过程基于对象头中的 Mark Word 变化实现。


🔄 锁升级的四个阶段

锁状态适用场景核心行为Mark Word 内容(简略)
无锁对象刚创建,没有线程竞争初始状态哈希码、分代年龄、锁标志位01(未偏向)
偏向锁只有一个线程反复进入同步块记录线程 ID,后续该线程重入零开销线程ID + 偏向时间戳 + 标志位01(偏向)
轻量级锁少量线程交替持有锁,竞争不激烈自旋等待(CAS),避免阻塞指向栈中锁记录的指针 + 标志位00
重量级锁多个线程激烈竞争,自旋失败或超时线程阻塞,依赖操作系统 mutex指向监视器(Monitor)指针 + 标志位10

⚙️ 升级触发条件详解

  1. 无锁 → 偏向锁

    • 第一个线程进入synchronized块时,JVM 通过 CAS 将当前线程 ID 写入对象头。

    • 前提:JVM 开启了偏向锁(默认有延迟,可用-XX:BiasedLockingStartupDelay=0关闭延迟)。

  2. 偏向锁 → 轻量级锁

    • 当第二个线程尝试竞争该偏向锁时,偏向锁失效。

    • 在全局安全点(Safepoint)撤销偏向锁:

      • 若原持有锁的线程已退出,则对象头置为无锁,再升级为轻量级锁(CAS 竞争)。

      • 若原线程仍存活且持有锁,则直接膨胀为轻量级锁,原线程持有锁,新线程开始自旋。

  3. 轻量级锁 → 重量级锁

    • 自旋超过一定次数(JDK 1.6 后为自适应自旋,根据历史情况动态调整)。

    • 自旋过程中又有新的线程加入竞争。

    • 持有锁的线程耗时过长,自旋浪费 CPU。

    • 此时锁膨胀为重量级锁,未获得锁的线程全部进入阻塞状态(BLOCKED)。


📌 注意事项

  • 锁升级不可逆:一旦升级为重量级锁,即使后续无竞争也不会降级。

  • 偏向锁在 JDK 15 后默认禁用并废弃(高并发下撤销成本过高)。

  • 哈希码会影响锁升级:若对象已调用hashCode(),对象头中存储了哈希码,则无法进入偏向锁状态(直接轻量级锁)。

  • 批量重偏向:当大量对象发生偏向锁撤销时,JVM 会批量重偏向或批量撤销,避免频繁 Safepoint。


💡 一句话总结

无锁 → 偏向锁(单线程)→ 轻量级锁(自旋)→ 重量级锁(阻塞),全部通过 CAS 修改对象头 Mark Word 实现,目的是在低竞争时避免操作系统互斥量开销。