锁升级的完整过程
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 VALUE
0 8 (object header: mark) 0x0000000000000001 (non-biasable; age: 0)
8 4 (object header: class) 0xf80001e5
12 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)
轻量级锁:
- 适合短时间同步块
- 长时间同步块可能导致过度自旋
重量级锁:
- 不可避免的高竞争场景
- 考虑改用并发容器或分段锁
理解锁升级过程有助于:
- 诊断性能问题
- 合理设计同步策略
- 优化高并发场景下的锁竞争