synchronized锁升级过程详解及观察方法
锁升级的完整过程
Java中的synchronized锁会经历以下升级过程(也称为锁膨胀):
- 无锁状态:新创建的对象初始状态
- 偏向锁:第一个线程访问时启用
- 轻量级锁:当有线程竞争时升级
- 重量级锁:竞争激烈时最终形态
1. 无锁 → 偏向锁
触发条件:
- 对象刚被创建
- 第一个线程访问同步块
实现机制:
- 在对象头Mark Word中记录偏向线程ID(23位)
- 设置偏向模式标志位(biased_lock=1,lock=01)
特点:
- 同一线程重入时不需任何同步操作
- 适合单线程访问场景
2. 偏向锁 → 轻量级锁
触发条件:
- 第二个线程尝试获取锁
- 偏向锁撤销(可通过JVM参数禁用)
实现机制:
- 暂停持有偏向锁的线程(安全点)
- 检查线程是否存活或已退出同步块
- 撤销偏向锁,升级为轻量级锁
- 在当前线程栈帧中创建锁记录(Lock Record)
- 使用CAS操作将对象头Mark Word替换为指向锁记录的指针
- 成功则获取锁,失败则自旋或进一步升级
特点:
- 使用CAS操作避免阻塞
- 适合线程交替执行的场景
- 自旋消耗CPU但避免了线程切换开销
3. 轻量级锁 → 重量级锁
触发条件:
- 自旋超过阈值(默认10次,可用-XX:PreBlockSpin调整)
- 等待线程数超过1(第三个线程尝试获取锁)
实现机制:
- 分配ObjectMonitor对象
- 设置对象头指向Monitor
- 竞争失败的线程进入Monitor的EntryList
- 线程状态由自旋变为阻塞
特点:
- 使用操作系统互斥量(mutex)实现
- 线程阻塞会引发上下文切换
- 适合高竞争场景
查看锁升级的方法
1. JVM参数观察
添加以下JVM参数查看锁状态变化:
-XX:+PrintFlagsFinal
-XX:+PrintSynchronization
-XX:+PrintBiasedLockingStatistics
-XX:+TraceBiasedLocking
2. jol工具分析对象头
使用Java Object Layout工具:
// 添加依赖
dependencies {implementation 'org.openjdk.jol:jol-core:0.16'
}// 查看对象头
public static void main(String[] args) {Object obj = new Object();System.out.println(ClassLayout.parseInstance(obj).toPrintable());synchronized (obj) {System.out.println(ClassLayout.parseInstance(obj).toPrintable());}
}
输出示例(64位JVM):
java.lang.Object object internals:
OFF SZ TYPE DESCRIPTION VALUE0 8 (object header: mark) 0x0000000000000001 (non-biasable; age: 0)8 4 (object header: class) 0xf80001e512 4 (object alignment gap)
Instance size: 16 bytes
3. 源码分析关键点
HotSpot JVM中相关源码文件:
- 偏向锁处理:
biasedLocking.cpp - 轻量级锁处理:
interpreterRuntime.cpp(fast_enter/exit) - 重量级锁处理:
synchronizer.cpp(slow_enter/exit)
关键方法调用链:
ObjectSynchronizer::fast_enter→ BiasedLocking::revoke_and_rebias→ InterpreterRuntime::monitorenter→ ObjectSynchronizer::slow_enter→ ObjectMonitor::enter
4. 性能监控工具
-
JConsole/VisualVM:
- 查看线程阻塞状态
- 监控锁竞争情况
-
Java Mission Control:
- 锁分析功能
- 详细的线程等待统计
-
async-profiler:
- 分析锁占用时间
- 监控上下文切换次数
实验观察锁升级
测试代码示例
public class LockUpgradeDemo {public static void main(String[] args) throws Exception {// 延迟偏向锁生效(默认JVM启动4秒后启用)Thread.sleep(5000);Object lock = new Object();System.out.println("初始状态:");printObjectHeader(lock);// 第一次加锁(偏向锁)synchronized (lock) {System.out.println("第一次加锁:");printObjectHeader(lock);}// 第二次加锁(仍为偏向锁)synchronized (lock) {System.out.println("同一线程重入:");printObjectHeader(lock);}// 第二个线程竞争(升级为轻量级锁)new Thread(() -> {synchronized (lock) {System.out.println("第二个线程加锁:");printObjectHeader(lock);}}).start();Thread.sleep(1000);// 多个线程竞争(升级为重量级锁)for (int i = 0; i < 5; i++) {new Thread(() -> {synchronized (lock) {try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}}}).start();}}private static void printObjectHeader(Object obj) {System.out.println(ClassLayout.parseInstance(obj).toPrintable());}
}
典型输出分析
-
初始状态:
0x0000000000000005 (biasable; age: 0)- 可偏向状态(但还未偏向任何线程)
-
第一次加锁:
0x00007f9b7002f805 (biased: 0x0000001ff3a0b400; epoch: 0; age: 0)- 已偏向当前线程(线程ID记录在mark word中)
-
第二个线程加锁:
0x000070000f9d6f10 (thin lock: 0x000070000f9d6f10)- 升级为轻量级锁(指向栈中锁记录的指针)
-
高竞争时:
0x00007f9b7002f80a (heavyweight lock)- 升级为重量级锁(指向ObjectMonitor的指针)
关键JVM参数
| 参数 | 说明 |
|---|---|
| -XX:+UseBiasedLocking | 启用偏向锁(JDK 15后默认禁用) |
| -XX:BiasedLockingStartupDelay | 偏向锁延迟启用时间(默认4000ms) |
| -XX:+PrintBiasedLockingStatistics | 打印偏向锁统计信息 |
| -XX:PreBlockSpin | 轻量级锁自旋次数(默认10) |
| -XX:+PrintPreciseBiasedLockingStatistics | 精确的偏向锁统计 |
生产环境建议
-
偏向锁:
- 单例对象、线程封闭对象可受益
- 高竞争场景下建议禁用(-XX:-UseBiasedLocking)
-
轻量级锁:
- 适合短时间同步块
- 长时间同步块可能导致过度自旋
-
重量级锁:
- 不可避免的高竞争场景
- 考虑改用并发容器或分段锁
理解锁升级过程有助于:
- 诊断性能问题
- 合理设计同步策略
- 优化高并发场景下的锁竞争