Java中的锁升级过程与synchronized实现原理详解
字数 1372 2025-11-21 10:10:13
Java中的锁升级过程与synchronized实现原理详解
1. 知识点描述
锁升级是synchronized关键字在JDK 6之后引入的重要优化机制,它通过从无锁状态逐步升级到重量级锁的方式,在不同竞争条件下采用不同的锁策略,有效平衡了性能开销和线程安全。理解这个过程需要掌握对象头结构、锁状态变迁以及底层实现原理。
2. 对象头结构(锁的存储基础)
- 每个Java对象在堆内存中都包含对象头,其中Mark Word是锁实现的关键
- Mark Word结构(以64位系统为例):
- 无锁状态:25位哈希码 + 4位分代年龄 + 1位偏向模式(0) + 2位锁标志(01)
- 偏向锁:54位线程ID + 2位Epoch + 1位分代年龄 + 1位偏向模式(1) + 2位锁标志(01)
- 轻量级锁:62位指向栈中锁记录的指针 + 2位锁标志(00)
- 重量级锁:62位指向监视器Monitor的指针 + 2位锁标志(10)
3. 锁升级的完整过程
步骤1:无锁状态
- 对象刚创建时处于无锁状态
- 当第一个线程访问同步代码块时,检查锁标志位为01,准备进入锁状态
步骤2:偏向锁(单线程优化)
- 目标:消除无竞争情况下的同步开销
- 具体过程:
- 线程检查对象头中的偏向模式位
- 如果为可偏向(匿名偏向状态),通过CAS操作将线程ID写入Mark Word
- 成功后,该线程再次进入同步块时只需检查线程ID是否匹配
- 匹配成功则直接执行,无需任何同步操作
- 优势:只有第一次需要CAS操作,后续几乎零开销
步骤3:轻量级锁(多线程交替执行)
- 触发条件:当有第二个线程尝试获取锁时,发生锁竞争
- 具体过程:
- 在安全点暂停拥有偏向锁的线程
- 检查原持有线程是否存活或已退出同步块
- 如果已退出,撤销偏向锁恢复到无锁状态
- 如果仍在执行,升级为轻量级锁:
- 在当前线程的栈帧中创建锁记录(Lock Record)
- 通过CAS将对象头的Mark Word复制到锁记录中
- 然后用CAS将对象头替换为指向锁记录的指针
- 轻量级锁通过CAS自旋尝试获取锁,适用于线程交替执行的场景
步骤4:重量级锁(激烈竞争)
- 触发条件:轻量级锁自旋失败(默认自旋10次或自适应自旋)
- 具体过程:
- 锁膨胀:创建ObjectMonitor对象(重量级锁的核心)
- 将对象头Mark Word指向ObjectMonitor
- 竞争线程进入等待队列,从用户态切换到内核态
- 依赖操作系统的互斥量实现线程阻塞和唤醒
- 特点:真正的互斥锁,开销大但能应对高并发场景
4. 锁的降级机制
- 重量级锁不会降级到轻量级锁(设计选择)
- 但存在批量重偏向和批量撤销优化:
- 当一类对象的偏向锁撤销次数超过阈值(默认20次)
- JVM会认为该类不适合使用偏向锁,后续对象直接使用轻量级锁
5. 实际应用示例
public class LockUpgradeExample {
private final Object lock = new Object();
private int count = 0;
public void increment() {
synchronized(lock) { // 这里会发生完整的锁升级过程
count++;
}
}
}
- 第一次执行:无锁 → 偏向锁
- 第二个线程进入:偏向锁 → 轻量级锁
- 多个线程竞争:轻量级锁 → 重量级锁
6. 优化建议
- 明确对象生命周期:短生命周期对象禁用偏向锁(-XX:-UseBiasedLocking)
- 控制锁粒度:减小同步代码块范围
- 避免不必要的同步:使用线程安全类或无锁编程
- 监控锁竞争:通过JStack等工具分析锁状态
这个锁升级过程体现了JVM在运行时根据实际竞争情况动态优化的能力,是理解Java并发编程底层实现的重要基础。