Java synchronized 锁升级全过程深度解析:从 Mark Word 到偏向锁、轻量级锁与重量级锁的 HotSpot 实现
synchronized 是 Java 最基础也是最重要的并发原语之一。很多人以为它只是简单的互斥锁;但实际上,在 HotSpot JVM 中,synchronized 是一个拥有复杂优化策略的“锁体系”,会在运行时根据竞争情况自动进行 无锁 → 偏向锁 → 轻量级锁 → 重量级锁 的多级演进(Lock Evolution)。
现代 JVM 对 synchronized 的优化,使其性能大幅提升,甚至在低竞争情况下性能超越 ReentrantLock。
本文将从 对象头布局、CAS 操作、JVM 源码、偏向锁撤销机制、轻量级锁自旋、自适应自旋、Monitor 膨胀逻辑等维度,深度解析 synchronized 的锁升级过程及其背后的 JVM 原理。
目录
- 对象头(Object Header)与 Mark Word:锁状态的载体
- synchronized 锁的四种状态与 Mark Word 完整位图
- 偏向锁(Biased Locking):无竞争的极致优化
- 偏向锁撤销机制(安全点、Batch Revoke)
- 轻量级锁(Thin Lock)与 CAS 自旋原理
- 重量级锁(Monitor)膨胀:OS 层阻塞
- 锁升级全路径图(可视化)
- synchronized 锁升级完整源码路径解析(重点)
- JDK 版本下锁机制差异(8/11/17/21)
- 实战:如何写出对锁友好的代码
- 总结:JVM 锁优化的核心思想
1. 对象头:锁状态的存储中心
HotSpot 的每个对象都有对象头(Object Header),主要包含两部分:
- Mark Word(存储锁状态、hashcode、GC 信息、线程 ID 等)
- Class Pointer(指向类元数据)
Mark Word 是锁的核心载体。
2. Mark Word 在不同锁状态下的位图结构(64bit)
2.1 无锁(Normal)
unused:25 | age:4 | biased_lock:0 | lock:01
2.2 偏向锁(Biased)
threadId:54 | epoch:2 | age:4 | biased_lock:1 | lock:01
2.3 轻量级锁(Thin Lock)
ptr_to_lock_record:62 | lock:00
- 指向线程栈中的 Lock Record
2.4 重量级锁(Monitor)
ptr_to_monitor:62 | lock:10
2.5 锁状态转换图
无锁 → 偏向锁 → 轻量级锁 → 重量级锁(不可降级)
3. 偏向锁(Biased Locking)
偏向锁是为了无竞争场景优化,让同一个线程重复加锁时不需要 CAS,自旋。
偏向锁获取流程:
- 检查锁标志位是否是偏向模式
- 检查 Mark Word 中是否记录当前线程 ID
- 如果是 → 直接进入同步块,不执行 CAS
偏向锁优势:
✔ 零成本加锁
✔ 高吞吐
适合:
- 单线程环境
- 绝大多数锁只在一个线程中使用(如 StringBuffer)
4. 偏向锁撤销(Lock Revoke)——核心高成本步骤
偏向锁的撤销是高成本的,因为:
✔ JVM 必须进入 全局安全点(Safepoint)
✔ 停顿所有线程
✔ 检查偏向锁线程的栈帧
✔ 将偏向锁转换为轻量级锁或无锁
HotSpot 关键源码(biasedLocking.cpp)
// 如果需要撤销偏向锁,要进入安全点
BiasedLocking::revoke_and_rebias(Handle obj, bool attempt_rebias, Thread * thread) {VMThread::execute(revoke);
}
在高竞争下,偏向锁会导致大量 safepoint,反而降低性能。
5. 轻量级锁(Thin Lock):CAS + 自旋
当第二个线程试图获取偏向锁时,偏向锁会被撤销并升级到轻量级锁。
轻量级锁的设计目标:
使用 CAS + 自旋,而不是 OS 阻塞/唤醒,提高性能。
获取轻量级锁流程:
- 在线程栈帧创建 Lock Record
- 使用 CAS 将对象头指向 Lock Record
- CAS 成功 → 获取锁
- CAS 失败 → 说明竞争发生 → 自旋
6. 自旋锁(Spinlock)与自适应自旋(Adaptive Spinning)
JVM 使用自旋来等待锁释放,而不是立即进入 OS 级阻塞。
为什么自旋更快?
- OS 阻塞/唤醒代价极高(上下文切换)
- 轻竞争场景中,锁释放很快,自旋即可等待到
但自旋过久,会造成 CPU 浪费
因此 JVM 加入 Adaptive Spinning:
- 如果该锁在历史上自旋成功 → 增加自旋次数
- 如果自旋失败较多 → 减少自旋次数
这是 JVM 运行时自调优的体现。
7. 重量级锁(Monitor):最终退路
当自旋失败或线程执行 wait(),JVM 会将锁升级到重量级锁。
重量级锁使用:
- ObjectMonitor
- OS Mutex + park/unpark 系统调用
HotSpot monitorenter 源码路径:
bytecodeInterpreter → InterpreterRuntime::monitorenter →
ObjectSynchronizer::enter → ObjectMonitor::enter
ObjectMonitor 内使用:
for (;;) {if (TryLock()) break;Park(); // 阻塞当前线程
}
阻塞会导致:
- 内核态切换
- 线程上下文保存与恢复
- 由 OS 负责调度
因此重量级锁的代价是最高的。
8. 锁升级全过程图(核心)
无竞争↓偏向锁(无 CAS)↓(竞争)偏向锁撤销 + Safepoint↓轻量级锁(CAS + 自旋)↓(自旋失败/锁等待)重量级锁(OS 互斥量)
锁永远不会降级。
9. synchronized 加锁完整源码路径解析(顶级干货)
以下是 synchronized 从字节码到 JVM 的完整流程。
9.1 字节码层
javac 会生成:
monitorenter
monitorexit
9.2 HotSpot 流程(精简)
monitorenter 执行流程(ObjectSynchronizer):
void ObjectSynchronizer::enter(Handle obj, BasicLock* lock, TRAPS) {if (obj is unlocked)CAS to lightweight lock;else if (can inflate)inflate and acquire monitor;elseslow_enter();
}
膨胀逻辑(inflate):
ObjectMonitor* ObjectSynchronizer::inflate() {// 分配 MonitorObjectMonitor* monitor = new ObjectMonitor();// 将对象头指向 Monitorobj->mark().set_monitor(monitor);
}
自旋逻辑请参考 ObjectMonitor::EnterI()。
以上是 synchronized 的底层真实执行路径。
10. JDK 版本行为变化
| JDK | 偏向锁 | 轻量级锁 | 重量级锁 | 自适应自旋 |
|---|---|---|---|---|
| 1.6 | 默认开启 | 支持 | 支持 | 支持 |
| 8 | 默认开启 | 支持 | 支持 | 完整 |
| 11 | 默认开启 | 支持 | 支持 | 完整 |
| 15 | 默认禁用 | 支持 | 支持 | 完整 |
| 17+ | 彻底移除偏向锁 | 支持 | 支持 | 完整 |
因此在 JDK 17+ 中锁升级路径简化为:
无锁 → 轻量级锁 → 重量级锁
但底层结构仍然相同。
11. 如何写出对 synchronized 友好的代码?
1. 避免过长的同步块
尽量缩小临界区:
synchronized(lock) {// 仅包含真正需要保护的代码
}
2. 避免锁对象频繁变化
锁对象应为 final/稳定对象。
3. 减少高竞争环境下的 synchronized
例如使用 ReentrantLock、LongAdder、ConcurrentHashMap 等更合适。
12. 总结:JVM 锁优化的核心思想
- 锁应该尽量无感(偏向锁)
- 轻竞争不陷入 OS(轻量级锁 + 自旋)
- 高竞争保证正确性(重量级锁)
- 锁只会升级,不会降级
- JVM 持续进行自适应优化(Adaptive heuristics)
