synchronized 锁升级是 JDK 6 引入的重要优化,通过偏向锁、轻量级锁、重量级锁的逐步升级策略,减少了传统重量级锁的性能开销。以下是锁升级的详细过程和原理:
1. 锁升级的核心动机
- 传统重量级锁的问题:依赖操作系统的互斥量(Mutex)实现,线程阻塞和唤醒需从用户态切换到内核态,性能损耗大。
- 优化思路:根据实际场景动态调整锁的粒度,避免不必要的重量级锁开销。
2. 锁状态与对象头
每个 Java 对象的对象头(Mark Word)中存储着锁状态信息,不同状态下 Mark Word 的结构如下:
| 锁状态 | 存储内容 | 标志位 |
|---|---|---|
| 无锁 | 对象哈希码、分代年龄 | 01 |
| 偏向锁 | 线程 ID、Epoch、分代年龄 | 01 |
| 轻量级锁 | 指向线程栈中锁记录的指针 | 00 |
| 重量级锁 | 指向 Monitor 的指针 | 10 |
| GC 标记 | 空 | 11 |
3. 锁升级的具体过程
(1)偏向锁(Biased Locking)
- 适用场景:只有一个线程访问同步块,无竞争。
- 获取过程:
- 当第一个线程 T1 访问同步块时,JVM 通过 CAS 操作将线程 ID 写入 Mark Word,同时将标志位设为
01(偏向锁)。 - 后续 T1 再次进入同步块时,无需任何同步操作,直接判断 Mark Word 中的线程 ID 是否为自己,若是则直接执行。
- 当第一个线程 T1 访问同步块时,JVM 通过 CAS 操作将线程 ID 写入 Mark Word,同时将标志位设为
- 撤销过程:
- 当有其他线程 T2 尝试竞争该锁时,T1 会被暂停,JVM 检查 T1 是否仍在执行同步块:
- 若已退出:Mark Word 恢复为无锁状态,T2 可竞争并获取偏向锁。
- 若仍在执行:偏向锁升级为轻量级锁,T1 继续执行,T2 自旋等待。
- 当有其他线程 T2 尝试竞争该锁时,T1 会被暂停,JVM 检查 T1 是否仍在执行同步块:
- 批量重偏向与撤销:
- 当一个类的对象频繁发生偏向锁撤销时,JVM 会认为该类不适合偏向锁,批量将该类的对象偏向锁撤销或禁用。
(2)轻量级锁(Lightweight Lock)
- 适用场景:多个线程交替访问同步块,无实际竞争。
- 加锁过程:
- 线程进入同步块前,在当前线程的栈帧中创建锁记录(Lock Record),并将 Mark Word 复制到锁记录中(Displaced Mark Word)。
- 通过 CAS 尝试将 Mark Word 更新为指向锁记录的指针:
- 成功:获取轻量级锁,Mark Word 标志位变为
00。 - 失败:表示有其他线程竞争,锁升级为重量级锁。
- 成功:获取轻量级锁,Mark Word 标志位变为
- 解锁过程:
- 通过 CAS 将锁记录中的 Displaced Mark Word 替换回 Mark Word。
- 若替换成功:锁释放完成。
- 若替换失败:表示有其他线程在竞争,已升级为重量级锁,需唤醒被阻塞的线程。
(3)重量级锁(Heavyweight Lock)
- 适用场景:多个线程同时竞争锁。
- 升级过程:
- 当轻量级锁竞争失败时,锁升级为重量级锁,Mark Word 存储指向 Monitor 的指针,标志位变为
10。 - 未获取到锁的线程会被阻塞(进入 Monitor 的 EntryList),释放 CPU 资源。
- 当轻量级锁竞争失败时,锁升级为重量级锁,Mark Word 存储指向 Monitor 的指针,标志位变为
- 释放过程:
- 持有锁的线程执行完同步块后,释放 Monitor。
- 唤醒 EntryList 中的线程重新竞争。
4. 锁升级的流程图
无锁状态 → 偏向锁(单线程) → 轻量级锁(多线程交替) → 重量级锁(多线程竞争)
5. 锁升级的关键点
- 不可逆性:锁只能升级,不能降级(但偏向锁可撤销为无锁)。
- 自旋锁(Spin Lock):轻量级锁竞争时,线程会短暂自旋等待锁释放,避免直接阻塞(JDK 6 后自旋次数可自适应)。
- 锁粗化(Lock Coarsening):JVM 会将多个连续的加锁、解锁操作合并为一个,减少锁的获取和释放次数。
- 锁消除(Lock Elimination):对不可能存在共享资源竞争的锁进行消除(如局部变量锁)。
6. 性能对比
| 锁类型 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| 偏向锁 | 无同步开销,单线程性能极佳 | 存在锁撤销开销 | 单线程访问同步块 |
| 轻量级锁 | 竞争不激烈时避免线程阻塞 | 自旋消耗 CPU | 多线程交替访问 |
| 重量级锁 | 保证线程安全,处理高竞争场景 | 线程阻塞和唤醒开销大 | 多线程同时竞争 |
7. 优化建议
- 减少锁竞争:通过减小锁粒度、缩短锁持有时间降低竞争。
- 批量偏向锁:对于频繁创建的对象,可通过 JVM 参数
-XX:+UseBiasedLocking启用偏向锁。 - 高并发场景:考虑使用
ReentrantLock或ConcurrentHashMap等更灵活的同步工具。
总结
锁升级机制通过动态调整锁的状态,在不同场景下平衡了性能和线程安全:
- 偏向锁:优化单线程场景,几乎无同步开销。
- 轻量级锁:处理多线程交替访问,避免内核态切换。
- 重量级锁:应对高竞争场景,保证线程安全。
理解锁升级原理有助于写出更高效的多线程代码,避免不必要的性能损耗。