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

发布于:2025-07-10 ⋅ 阅读:(22) ⋅ 点赞:(0)

锁升级的完整过程

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               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中相关源码文件:

  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. 重量级锁

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

理解锁升级过程有助于:

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

网站公告

今日签到

点亮在社区的每一天
去签到