Java中的锁膨胀过程与synchronized实现原理详解
字数 1105 2025-11-29 11:43:59
Java中的锁膨胀过程与synchronized实现原理详解
描述
锁膨胀是synchronized关键字在JVM中的核心优化机制,描述了锁状态从无锁到偏向锁、轻量级锁,最终到重量级锁的升级过程。理解这一过程对掌握Java并发编程和性能优化至关重要。
锁的基本概念
- synchronized是Java中的内置锁,保证同一时刻只有一个线程能执行同步代码
- JVM为了平衡性能与安全性,设计了多级锁机制
- 锁膨胀是单向过程:无锁 → 偏向锁 → 轻量级锁 → 重量级锁
对象头结构
- 每个Java对象在内存中都有对象头(Object Header)
- 对象头包含:
- Mark Word:存储对象的哈希码、分代年龄、锁状态等信息
- 类型指针:指向对象所属的类元数据
- 不同锁状态下,Mark Word的内容会变化
锁膨胀详细过程
阶段一:无锁状态
- 新创建的对象处于无锁状态
- Mark Word包含:哈希码、分代年龄、锁标志位01
- 此时任何线程都可以直接访问对象
阶段二:偏向锁
- 触发条件:第一个线程访问同步代码时
- 实现机制:
- 在Mark Word中记录偏向线程ID
- 设置偏向模式标志位
- 锁标志位仍为01(通过额外位区分)
- 优点:同一线程重入时无需任何同步操作
- 偏斜撤销:当其他线程尝试获取锁时,需要撤销偏向锁
阶段三:轻量级锁
- 触发条件:多个线程交替执行,但没有真正竞争
- 加锁过程:
- 在当前线程栈帧中创建锁记录(Lock Record)
- 将对象头的Mark Word复制到锁记录中
- 使用CAS操作将对象头指向锁记录
- 解锁过程:
- 使用CAS将Mark Word还原回对象头
- 如果成功,说明没有竞争;如果失败,说明存在竞争,需要膨胀
- 优点:避免操作系统级线程阻塞
阶段四:重量级锁
- 触发条件:多个线程存在真实竞争
- 实现机制:
- 指向操作系统层面的互斥量(mutex)
- 未获取锁的线程进入阻塞状态
- 涉及用户态到内核态的切换,开销较大
- Monitor对象:
- 每个对象都与一个Monitor关联
- Monitor包含:拥有者线程、入口计数、等待队列等
- 锁标志位变为10
锁膨胀的逆向过程
- 锁膨胀是单向的,不能降级(在早期版本中)
- 在特定条件下,重量级锁可以降级为轻量级锁(现代JVM优化)
性能影响与优化建议
- 偏向锁适合单线程重复访问的场景
- 轻量级锁适合低竞争的多线程环境
- 重量级锁适合高竞争场景
- 可通过JVM参数调整锁策略:
- -XX:-UseBiasedLocking:禁用偏向锁
- -XX:-UseSpinning:禁用自旋优化
实际应用示例
public class LockExample {
private final Object lock = new Object();
private int count = 0;
public void increment() {
synchronized(lock) { // 这里会发生锁膨胀
count++;
}
}
}
理解锁膨胀过程有助于编写更高效的并发代码,并在性能调优时做出合理决策。