synchronized锁升级的过程(从无锁到偏向锁,再到轻量级锁,最后到重量级锁的一个过程)
锁升级是 Java 中 synchronized 锁 的核心优化机制(基于 JVM 的 对象头 Mark Word
实现),指锁的状态从 无锁 → 偏向锁 → 轻量级锁 → 重量级锁 逐步升级的过程。其目的是通过 “按需升级”,在不同并发场景下选择最优的锁实现,平衡性能与线程安全。
一、锁的四种状态
在讲解升级过程前,需先明确 synchronized 锁的四种状态(按开销从小到大排序):
锁状态 | 核心特点 | 适用场景 |
---|---|---|
无锁 | 无锁竞争,对象未被任何线程锁定 | 单线程访问 |
偏向锁 | 仅记录第一个获取锁的线程 ID,后续该线程可直接重入,无需竞争 | 单线程重复获取锁(无并发) |
轻量级锁 | 多线程交替获取锁,通过 CAS 操作 竞争锁,避免内核态切换 | 低并发(线程交替执行) |
重量级锁 | 多线程同时竞争锁,依赖操作系统 互斥量(Mutex),会阻塞线程 | 高并发(线程频繁竞争) |
二、锁升级的完整过程
锁升级的触发条件是 “并发竞争加剧”,JVM 会根据线程竞争情况,自动将锁从低开销状态升级到高开销状态,且升级过程是 单向的(不可逆)(如偏向锁升级为轻量级锁后,不会再降级为偏向锁)。
1. 第一步:无锁 → 偏向锁
- 触发场景:单线程首次获取 synchronized 锁时,JVM 为了减少锁开销,会将锁初始化为 “偏向锁”。
- 核心操作:
- 线程 A 尝试获取锁时,检查对象头的
Mark Word
(存储对象锁状态的字段),发现当前是 “无锁” 状态。 - 通过 CAS 操作,将
Mark Word
中的 “锁状态” 改为 “偏向锁”,并记录线程 A 的 ID(threadId
)和 “偏向时间戳”。 - 后续线程 A 再次进入 synchronized 代码块时,只需对比
Mark Word
中的线程 ID 是否为自己:- 是:直接重入锁,无需任何 CAS 或阻塞操作(开销极低)。
- 否:触发偏向锁撤销,进入下一步升级。
- 线程 A 尝试获取锁时,检查对象头的
2. 第二步:偏向锁 → 轻量级锁
- 触发场景:当有 第二个线程(线程 B)尝试获取同一把锁 时,偏向锁的 “单线程假设” 被打破,JVM 会撤销偏向锁,升级为 “轻量级锁”。
- 核心操作:
- 线程 B 尝试获取锁时,发现
Mark Word
记录的是线程 A 的 ID(偏向锁状态),且线程 A 可能仍在执行(或已退出但未清理偏向锁)。 - JVM 首先会 暂停线程 A,检查线程 A 的状态:
- 若线程 A 已退出:将
Mark Word
重置为 “无锁”,线程 B 重新尝试 CAS 获取轻量级锁。 - 若线程 A 仍在执行:撤销偏向锁(将
Mark Word
中的偏向状态清除),进入轻量级锁竞争。
- 若线程 A 已退出:将
- 轻量级锁的竞争逻辑:
- 线程 A 和线程 B 会分别在自己的 栈帧 中创建一个 “锁记录(Lock Record)”,存储对象头
Mark Word
的副本(称为Displaced Mark Word
)。 - 线程通过 CAS 操作,尝试将对象头的
Mark Word
指向自己的 “锁记录地址”:- 成功:获取轻量级锁,执行同步代码。
- 失败:判断当前锁是否仍为轻量级锁(未升级),若仍是,则自旋重试 CAS(避免阻塞);若自旋次数超过阈值(默认 10 次,或自适应自旋),则进入下一步升级。
- 线程 A 和线程 B 会分别在自己的 栈帧 中创建一个 “锁记录(Lock Record)”,存储对象头
- 线程 B 尝试获取锁时,发现
3. 第三步:轻量级锁 → 重量级锁
- 触发场景:当 多个线程同时竞争锁(如线程 A、B、C 同时尝试获取锁),或轻量级锁的 CAS 自旋重试失败 时,轻量级锁的 “交替执行假设” 被打破,JVM 会将锁升级为 “重量级锁”。
- 核心操作:
- 线程 C 尝试 CAS 获取轻量级锁时,发现对象头的
Mark Word
已指向线程 A 的锁记录(线程 A 持有轻量级锁),且自旋重试多次后仍未成功。 - JVM 会将轻量级锁的 “锁状态” 改为 “重量级锁”,并将对象头的
Mark Word
指向 操作系统的互斥量(Mutex) 地址。 - 此时未获取到锁的线程(如 B、C)会 放弃 CAS 自旋,转而调用操作系统的
park()
方法,进入 阻塞状态(内核态切换,开销高)。 - 当持有锁的线程 A 释放锁时,会通过操作系统的
unpark()
方法,唤醒阻塞队列中的一个线程(如 B),线程 B 重新竞争重量级锁。
- 线程 C 尝试 CAS 获取轻量级锁时,发现对象头的
三、关键细节:对象头 Mark Word 的角色
锁升级的本质是 对象头 Mark Word 的状态变化,不同锁状态下,Mark Word
的存储内容不同(以 64 位 JVM 为例):
锁状态 | Mark Word 存储内容(简化) | 锁状态标识位 |
---|---|---|
无锁 | 对象哈希码(hashCode) + 无锁标识 | 01 |
偏向锁 | 偏向线程 ID + 偏向时间戳 + 偏向锁标识 | 01(特殊标记) |
轻量级锁 | 指向线程栈帧中 “锁记录” 的指针 + 轻量级锁标识 | 00 |
重量级锁 | 指向操作系统 “互斥量” 的指针 + 重量级锁标识 | 10 |
JVM 通过读取 Mark Word
的 “锁状态标识位”,即可判断当前锁的状态,进而执行对应的升级逻辑。
四、锁升级的核心目的
锁升级的设计思想是 “因地制宜”:
单线程场景:用偏向锁最小化锁开销(几乎无成本)。
低并发场景:用轻量级锁的 CAS 操作避免线程阻塞(用户态操作,开销低)。
高并发场景:用重量级锁的互斥量保证线程安全(虽开销高,但能稳定处理竞争)。
通过这种 “按需升级” 的机制,synchronized 锁在不同并发场景下都能兼顾性能与安全性,避免了 “一刀切” 的锁开销问题(如早期 synchronized 直接使用重量级锁,单线程场景下也有高开销)。