![图片[1]-14 JVM synchronized实现原理:从对象头到锁升级-速优课](http://www.suyouke.com/wp-content/uploads/2026/07/doubao_img_2304x1728_20260722_174942-1-1024x768.png)
synchronized 是 Java 中最基本的同步机制,用于保证方法或代码块的原子性、可见性和有序性。本文将深入探讨 synchronized 的底层实现原理,包括对象头结构、锁升级机制以及各种锁状态的特点。
一、synchronized 的使用方式
同步方法
public synchronized void method() {
// 同步代码块
}
同步代码块
public void method() {
synchronized (this) {
// 同步代码块
}
}
类锁
public static synchronized void staticMethod() {
// 同步代码块
}
二、对象头与锁状态
对象头结构
在 HotSpot 虚拟机中,对象头由两部分组成:
| 部分 | 大小 | 说明 |
|---|---|---|
| Mark Word | 8 字节(64位) | 存储锁状态、哈希码、分代年龄等 |
| Class Pointer | 8 字节(64位) | 指向类的元数据 |
Mark Word 的锁状态
| 锁状态 | 锁标志 | Mark Word 内容 |
|---|---|---|
| 无锁 | 01 | 对象哈希码(25位)+ 分代年龄(4位)+ 是否偏向(1位=0)+ 锁标志(2位=01) |
| 偏向锁 | 01 | 线程 ID(54位)+ epoch(2位)+ 分代年龄(4位)+ 是否偏向(1位=1)+ 锁标志(2位=01) |
| 轻量级锁 | 00 | 指向栈中锁记录的指针(62位)+ 锁标志(2位=00) |
| 重量级锁 | 10 | 指向重量级锁(Monitor)的指针(62位)+ 锁标志(2位=10) |
| GC 标记 | 11 | 空(62位)+ 锁标志(2位=11) |
三、锁升级机制
锁升级流程
无锁 → 偏向锁 → 轻量级锁 → 重量级锁
↑ ↑
└────────────────────┘
不可降级
无锁状态
对象刚创建时处于无锁状态:
- Mark Word 存储对象的哈希码和分代年龄
- 是否偏向位为 0
偏向锁
触发条件:
- 对象被第一次访问时
- 只有一个线程访问对象
获取偏向锁:
- 检查对象的偏向锁标志是否为 1
- 如果是,检查线程 ID 是否为当前线程
- 如果是,直接执行同步代码
- 如果不是,尝试 CAS 获取偏向锁
释放偏向锁:
- 偏向锁不会主动释放
- 只有在其他线程尝试获取锁时才会撤销
轻量级锁
触发条件:
- 多个线程交替访问对象
- 没有长时间的锁竞争
获取轻量级锁:
- 在当前线程的栈帧中创建锁记录(Lock Record)
- 将对象的 Mark Word 复制到锁记录中
- 使用 CAS 将对象的 Mark Word 指向锁记录
- 如果 CAS 成功,获取锁;如果失败,自旋等待
释放轻量级锁:
- 使用 CAS 将对象的 Mark Word 恢复为锁记录中的值
- 如果 CAS 成功,释放锁;如果失败,说明有竞争,升级为重量级锁
重量级锁
触发条件:
- 多个线程长时间竞争锁
- 轻量级锁自旋次数超过阈值
获取重量级锁:
- 将对象的 Mark Word 指向 Monitor 对象
- 当前线程进入 Monitor 的等待队列
- 线程阻塞,等待被唤醒
释放重量级锁:
- 当前线程释放 Monitor
- 唤醒等待队列中的一个线程
- 被唤醒的线程尝试获取锁
四、Monitor 对象
Monitor 的结构
Monitor(管程)是重量级锁的核心数据结构:
ObjectMonitor {
_header // 对象头
_count // 锁计数器(重入次数)
_waiters // 等待队列
_recursions // 重入次数
_owner // 持有锁的线程
_EntryList // 入口队列(等待获取锁的线程)
_WaitSet // 等待队列(调用 wait() 的线程)
}
Monitor 的工作流程
// 获取锁
monitorenter:
if (_owner == null) {
_owner = currentThread;
_count = 1;
} else if (_owner == currentThread) {
_count++; // 重入
} else {
currentThread.block(); // 阻塞
}
// 释放锁
monitorexit:
_count--;
if (_count == 0) {
_owner = null;
wakeup(_EntryList); // 唤醒等待线程
}
五、锁优化技术
自旋锁
在升级为重量级锁之前,线程会自旋等待:
- 默认自旋次数为 10 次
- 使用
-XX:PreBlockSpin参数调整 - Java 6 引入自适应自旋,根据历史情况动态调整
锁消除
JIT 编译器会消除不必要的锁:
- 如果锁对象不会逃逸出当前方法
- 如果锁对象只被一个线程访问
- 使用
-XX:+EliminateLocks参数启用
锁粗化
JIT 编译器会合并相邻的锁:
- 将多次加锁解锁合并为一次
- 减少锁的开销
偏向锁优化
Java 6 默认启用偏向锁:
- 使用
-XX:+UseBiasedLocking参数启用 - 使用
-XX:BiasedLockingStartupDelay参数设置延迟时间
六、synchronized 的性能对比
不同锁状态的性能
| 锁状态 | 获取开销 | 释放开销 | 适用场景 |
|---|---|---|---|
| 无锁 | 无 | 无 | 单线程访问 |
| 偏向锁 | 极低 | 极低 | 单线程重复访问 |
| 轻量级锁 | 低 | 低 | 多线程交替访问 |
| 重量级锁 | 高 | 高 | 多线程竞争 |
synchronized vs ReentrantLock
| 特性 | synchronized | ReentrantLock |
|---|---|---|
| 锁类型 | 独占锁 | 独占锁 |
| 可重入 | 是 | 是 |
| 公平性 | 非公平 | 可配置 |
| 可中断 | 否 | 是 |
| 超时获取 | 否 | 是 |
| 条件变量 | 单一 | 多个 |
| 性能 | 高(JDK 6+) | 高 |
七、总结
synchronized 的实现涉及多个层面:
- 对象头:Mark Word 存储锁状态信息
- 锁升级:无锁 → 偏向锁 → 轻量级锁 → 重量级锁
- Monitor:重量级锁的核心数据结构
- 锁优化:自旋锁、锁消除、锁粗化、偏向锁
关键要点:
- JDK 6 对 synchronized 进行了重大优化,性能大幅提升
- 锁升级是单向的,不会降级
- 选择合适的锁类型可以提高程序性能
理解 synchronized 的底层实现,有助于编写更高效的并发代码。
© 版权声明
文章版权归作者所有,未经允许请勿转载。
THE END








请登录后查看评论内容