Java 锁升级过程:偏向锁到轻量级锁到重量级锁
Java 虚拟机(JVM)为了提升多线程环境下对象同步的性能,设计了一套锁升级机制。这套机制从最轻量的“偏向锁”开始,在竞争加剧时逐步升级为“轻量级锁”,最终在高竞争场景下转为“重量级锁”。整个过程对开发者透明,但理解其原理有助于写出更高效的并发代码。
什么是锁?为什么需要升级?
在 Java 中,synchronized 关键字用于实现线程同步。早期 JVM 直接依赖操作系统的互斥量(Mutex)实现,这种锁被称为“重量级锁”,因为每次获取或释放都需要用户态与内核态切换,开销很大。
为减少不必要的性能损耗,JVM 引入了锁升级策略:只有在真正发生竞争时,才使用更重的锁机制。若一个对象长期只被一个线程访问,则无需复杂同步逻辑。
第一步:偏向锁(Biased Locking)
偏向锁的核心思想是:假设未来还会由同一个线程加锁,因此直接“偏向”它,免去后续同步开销。
- 当一个线程首次执行
synchronized代码块时,JVM 检查对象头中的 Mark Word。 - 如果 Mark Word 处于“可偏向”状态(即未锁定且未被其他线程偏向),JVM 将当前线程 ID 记录到 Mark Word 中,并将锁标志位设为“偏向锁”。
- 此后该线程再次进入同一
synchronized块时,只需比对 Mark Word 中的线程 ID 是否匹配。若匹配,直接执行临界区代码,无需任何 CAS(Compare-And-Swap)操作或系统调用。
注意:偏向锁默认在 JVM 启动后延迟 4 秒才启用(可通过
-XX:BiasedLockingStartupDelay=0禁用延迟)。某些场景(如大量使用synchronized但无竞争)可受益;但在高并发或频繁线程切换场景下,偏向锁反而增加撤销成本,可通过-XX:-UseBiasedLocking完全关闭。
第二步:轻量级锁(Lightweight Locking)
当第二个线程尝试获取已被偏向的对象锁时,偏向锁失效,升级为轻量级锁。
- 第二个线程发现对象已被其他线程偏向,于是发起“撤销偏向”(Bias Revocation)。
- JVM 暂停拥有偏向锁的线程(安全点 Safepoint),检查该线程是否仍在执行同步块:
- 若已退出,则直接将锁转为“无锁”状态,新线程重新竞争。
- 若仍在执行,则将锁升级为轻量级锁。
- 轻量级锁通过 CAS 操作实现:
- 每个线程在栈帧中创建一个 Lock Record。
- 尝试用 CAS 将对象头的 Mark Word 替换为指向自己 Lock Record 的指针。
- 若 CAS 成功,获得锁;若失败,说明有竞争,继续自旋重试(默认最多 10 次,由
-XX:PreBlockSpin控制)。
轻量级锁适用于短时间、低竞争的同步场景。自旋避免了线程挂起/唤醒的开销,但如果自旋多次仍未获得锁,就会进一步升级。
第三步:重量级锁(Heavyweight Locking)
当轻量级锁自旋超过阈值,或多个线程同时竞争时,锁升级为重量级锁。
- 竞争线程在自旋一定次数后仍未获得锁,JVM 将其挂起(阻塞),并进入操作系统等待队列。
- 对象头中的 Mark Word 被替换为指向 Monitor 对象的指针(Monitor 是 JVM 内部用于实现重量级锁的数据结构)。
- Monitor 包含一个
_owner字段(记录持有锁的线程)、一个_EntryList(阻塞队列)和一个_WaitSet(调用wait()的线程集合)。 - 后续所有线程请求该锁时,都会被放入
_EntryList,由操作系统调度器决定何时唤醒。
重量级锁的代价最高,但能保证在高并发下的公平性和稳定性。
锁状态在对象头中的表示
Java 对象在内存中的布局包含“对象头”,其中 Mark Word 存储锁信息。不同锁状态下,Mark Word 的内容如下:
| 锁状态 | Mark Word 最后三位(64位 JVM) | 存储内容 |
|---|---|---|
| 无锁 | 001 |
对象哈希码、分代年龄等 |
| 偏向锁 | 101 |
线程 ID、Epoch、分代年龄等 |
| 轻量级锁 | 000 |
指向线程栈中 Lock Record 的指针 |
| 重量级锁 | 010 |
指向 Monitor 对象的指针 |
| GC 标记 | 111 |
— |
注:实际位模式可能因 JVM 版本略有差异,但逻辑一致。
锁升级的不可逆性
锁升级是单向的:偏向锁 → 轻量级锁 → 重量级锁,不可降级。
- 一旦升级为重量级锁,即使后续无竞争,也不会退回轻量级或偏向状态。
- 这是因为 Monitor 结构一旦分配,就难以安全回收;而偏向锁撤销本身也有成本。
因此,在高并发服务中,若已知存在多线程竞争,建议关闭偏向锁以避免不必要的撤销开销。
如何验证锁状态?
可通过以下方式观察锁升级过程:
-
编写测试代码:
public class LockDemo { private static final Object lock = new Object(); public static void main(String[] args) throws InterruptedException { // 单线程:应为偏向锁 synchronized (lock) { System.out.println("First thread holding lock"); } Thread.sleep(5000); // 等待偏向锁生效 // 启动第二个线程触发升级 Thread t1 = new Thread(() -> { synchronized (lock) { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } }); t1.start(); t1.join(); } } -
启动 JVM 时添加参数:
-XX:+PrintAssembly -XX:+TraceClassLoading -XX:+PrintBiasedLockingStatistics或使用
jol(Java Object Layout)工具打印对象头:java -jar jol-cli/target/jol-cli.jar internals LockDemo
性能建议
- 避免不必要的同步:缩小
synchronized块范围,减少锁持有时间。 - 高并发场景禁用偏向锁:添加 JVM 参数
-XX:-UseBiasedLocking。 - 考虑使用
java.util.concurrent包中的工具类(如ReentrantLock、AtomicInteger),它们基于 CAS 和队列算法,通常比synchronized更灵活高效。 - 不要依赖锁升级细节编写业务逻辑:这是 JVM 内部优化机制,行为可能随版本变化。

暂无评论,快来抢沙发吧!