ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

synchronized锁升级过程详解及观察方法

2026/10/3 4:55:37 拓冰建站 浏览量
synchronized锁升级过程详解及观察方法

锁升级的完整过程

Java中的synchronized锁会经历以下升级过程(也称为锁膨胀):

  1. 无锁状态:新创建的对象初始状态
  2. 偏向锁:第一个线程访问时启用
  3. 轻量级锁:当有线程竞争时升级
  4. 重量级锁:竞争激烈时最终形态

1. 无锁 → 偏向锁

触发条件:

  • 对象刚被创建
  • 第一个线程访问同步块

实现机制:

  • 在对象头Mark Word中记录偏向线程ID(23位)
  • 设置偏向模式标志位(biased_lock=1,lock=01)

特点:

  • 同一线程重入时不需任何同步操作
  • 适合单线程访问场景

2. 偏向锁 → 轻量级锁

触发条件:

  • 第二个线程尝试获取锁
  • 偏向锁撤销(可通过JVM参数禁用)

实现机制:

  1. 暂停持有偏向锁的线程(安全点)
  2. 检查线程是否存活或已退出同步块
  3. 撤销偏向锁,升级为轻量级锁
    • 在当前线程栈帧中创建锁记录(Lock Record)
    • 使用CAS操作将对象头Mark Word替换为指向锁记录的指针
    • 成功则获取锁,失败则自旋或进一步升级

特点:

  • 使用CAS操作避免阻塞
  • 适合线程交替执行的场景
  • 自旋消耗CPU但避免了线程切换开销

3. 轻量级锁 → 重量级锁

触发条件:

  • 自旋超过阈值(默认10次,可用-XX:PreBlockSpin调整)
  • 等待线程数超过1(第三个线程尝试获取锁)

实现机制:

  1. 分配ObjectMonitor对象
  2. 设置对象头指向Monitor
  3. 竞争失败的线程进入Monitor的EntryList
  4. 线程状态由自旋变为阻塞

特点:

  • 使用操作系统互斥量(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中相关源码文件:

  1. 偏向锁处理:biasedLocking.cpp
  2. 轻量级锁处理:interpreterRuntime.cpp (fast_enter/exit)
  3. 重量级锁处理:synchronizer.cpp (slow_enter/exit)

关键方法调用链:

ObjectSynchronizer::fast_enter→ BiasedLocking::revoke_and_rebias→ InterpreterRuntime::monitorenter→ ObjectSynchronizer::slow_enter→ ObjectMonitor::enter

4. 性能监控工具

  1. JConsole/VisualVM:

    • 查看线程阻塞状态
    • 监控锁竞争情况
  2. Java Mission Control:

    • 锁分析功能
    • 详细的线程等待统计
  3. 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());}
}

典型输出分析

  1. 初始状态:

    0x0000000000000005 (biasable; age: 0)
    
    • 可偏向状态(但还未偏向任何线程)
  2. 第一次加锁:

    0x00007f9b7002f805 (biased: 0x0000001ff3a0b400; epoch: 0; age: 0)
    
    • 已偏向当前线程(线程ID记录在mark word中)
  3. 第二个线程加锁:

    0x000070000f9d6f10 (thin lock: 0x000070000f9d6f10)
    
    • 升级为轻量级锁(指向栈中锁记录的指针)
  4. 高竞争时:

    0x00007f9b7002f80a (heavyweight lock)
    
    • 升级为重量级锁(指向ObjectMonitor的指针)

关键JVM参数

参数说明
-XX:+UseBiasedLocking启用偏向锁(JDK 15后默认禁用)
-XX:BiasedLockingStartupDelay偏向锁延迟启用时间(默认4000ms)
-XX:+PrintBiasedLockingStatistics打印偏向锁统计信息
-XX:PreBlockSpin轻量级锁自旋次数(默认10)
-XX:+PrintPreciseBiasedLockingStatistics精确的偏向锁统计

生产环境建议

  1. 偏向锁:

    • 单例对象、线程封闭对象可受益
    • 高竞争场景下建议禁用(-XX:-UseBiasedLocking)
  2. 轻量级锁:

    • 适合短时间同步块
    • 长时间同步块可能导致过度自旋
  3. 重量级锁:

    • 不可避免的高竞争场景
    • 考虑改用并发容器或分段锁

理解锁升级过程有助于:

  • 诊断性能问题
  • 合理设计同步策略
  • 优化高并发场景下的锁竞争