Java synchronized 锁升级全过程深度解析:从 Mark Word 到偏向锁、轻量级锁与重量级锁的 HotSpot 实现

synchronized 是 Java 最基础也是最重要的并发原语之一。很多人以为它只是简单的互斥锁;但实际上,在 HotSpot JVM 中,synchronized 是一个拥有复杂优化策略的"锁体系",会在运行时根据竞争情况自动进行 无锁 → 偏向锁 → 轻量级锁 → 重量级锁 的多级演进(Lock Evolution)。

现代 JVM 对 synchronized 的优化,使其性能大幅提升,甚至在低竞争情况下性能超越 ReentrantLock。

本文将从 对象头布局、CAS 操作、JVM 源码、偏向锁撤销机制、轻量级锁自旋、自适应自旋、Monitor 膨胀逻辑等维度,深度解析 synchronized 的锁升级过程及其背后的 JVM 原理。


目录

  1. 对象头(Object Header)与 Mark Word:锁状态的载体
  2. synchronized 锁的四种状态与 Mark Word 完整位图
  3. 偏向锁(Biased Locking):无竞争的极致优化
  4. 偏向锁撤销机制(安全点、Batch Revoke)
  5. 轻量级锁(Thin Lock)与 CAS 自旋原理
  6. 重量级锁(Monitor)膨胀:OS 层阻塞
  7. 锁升级全路径图(可视化)
  8. synchronized 锁升级完整源码路径解析(重点)
  9. JDK 版本下锁机制差异(8/11/17/21)
  10. 实战:如何写出对锁友好的代码
  11. 总结:JVM 锁优化的核心思想

1. 对象头:锁状态的存储中心

HotSpot 的每个对象都有对象头(Object Header),主要包含两部分:

  1. Mark Word(存储锁状态、hashcode、GC 信息、线程 ID 等)
  2. 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,自旋。

偏向锁获取流程:

  1. 检查锁标志位是否是偏向模式
  2. 检查 Mark Word 中是否记录当前线程 ID
  3. 如果是 → 直接进入同步块,不执行 CAS

偏向锁优势:

✔ 零成本加锁

✔ 高吞吐

适合:

  • 单线程环境
  • 绝大多数锁只在一个线程中使用(如 StringBuffer)

4. 偏向锁撤销(Lock Revoke)------核心高成本步骤

偏向锁的撤销是高成本的,因为:

✔ JVM 必须进入 全局安全点(Safepoint)

✔ 停顿所有线程

✔ 检查偏向锁线程的栈帧

✔ 将偏向锁转换为轻量级锁或无锁

HotSpot 关键源码(biasedLocking.cpp)

cpp 复制代码
// 如果需要撤销偏向锁,要进入安全点
BiasedLocking::revoke_and_rebias(Handle obj, bool attempt_rebias, Thread * thread) {
    VMThread::execute(revoke);
}

在高竞争下,偏向锁会导致大量 safepoint,反而降低性能。


5. 轻量级锁(Thin Lock):CAS + 自旋

当第二个线程试图获取偏向锁时,偏向锁会被撤销并升级到轻量级锁。

轻量级锁的设计目标:

使用 CAS + 自旋,而不是 OS 阻塞/唤醒,提高性能。

获取轻量级锁流程:

  1. 在线程栈帧创建 Lock Record
  2. 使用 CAS 将对象头指向 Lock Record
  3. CAS 成功 → 获取锁
  4. 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 内使用:

cpp 复制代码
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):
cpp 复制代码
void ObjectSynchronizer::enter(Handle obj, BasicLock* lock, TRAPS) {
    if (obj is unlocked)
        CAS to lightweight lock;
    else if (can inflate)
        inflate and acquire monitor;
    else
        slow_enter();
}
膨胀逻辑(inflate):
cpp 复制代码
ObjectMonitor* ObjectSynchronizer::inflate() {
    // 分配 Monitor
    ObjectMonitor* monitor = new ObjectMonitor();
    // 将对象头指向 Monitor
    obj->mark().set_monitor(monitor);
}

自旋逻辑请参考 ObjectMonitor::EnterI()

以上是 synchronized 的底层真实执行路径。


10. JDK 版本行为变化

JDK 偏向锁 轻量级锁 重量级锁 自适应自旋
1.6 默认开启 支持 支持 支持
8 默认开启 支持 支持 完整
11 默认开启 支持 支持 完整
15 默认禁用 支持 支持 完整
17+ 彻底移除偏向锁 支持 支持 完整

因此在 JDK 17+ 中锁升级路径简化为:

复制代码
无锁 → 轻量级锁 → 重量级锁

但底层结构仍然相同。


11. 如何写出对 synchronized 友好的代码?

1. 避免过长的同步块

尽量缩小临界区:

java 复制代码
synchronized(lock) {
    // 仅包含真正需要保护的代码
}

2. 避免锁对象频繁变化

锁对象应为 final/稳定对象。

3. 减少高竞争环境下的 synchronized

例如使用 ReentrantLock、LongAdder、ConcurrentHashMap 等更合适。


12. 总结:JVM 锁优化的核心思想

  1. 锁应该尽量无感(偏向锁)
  2. 轻竞争不陷入 OS(轻量级锁 + 自旋)
  3. 高竞争保证正确性(重量级锁)
  4. 锁只会升级,不会降级
  5. JVM 持续进行自适应优化(Adaptive heuristics)
相关推荐
楚疏笃2 小时前
纯Python 实现 Word 文档转换 Markdown
python·word
基哥的奋斗历程2 小时前
Kotlin_Flow_完整使用指南
android·开发语言·kotlin
q***06292 小时前
搭建Golang gRPC环境:protoc、protoc-gen-go 和 protoc-gen-go-grpc 工具安装教程
开发语言·后端·golang
阿Y加油吧3 小时前
Java SE核心面试题总结——day 01
java
布丁写代码3 小时前
GESP C++ 一级 2025年09月真题解析
开发语言·c++·程序人生·学习方法
2021_fc3 小时前
Flink入门指南:使用Java构建第一个Flink应用
java·大数据·flink
GOTXX3 小时前
用Rust实现一个简易的rsync(远程文件同步)工具
开发语言·后端·rust
诸葛亮的芭蕉扇3 小时前
抓图巡检-底图支持绘制
开发语言·前端·javascript
Java开发追求者3 小时前
vscode导入springboot项目
java·ide·spring boot·vscode