锁升级机制全解析:偏向锁、轻量级锁、重量级锁的秘密

摘要

在 HotSpot JVM 中,synchronized 并不是一开始就进入重量级锁。为了提升性能,JVM 设计了 锁升级机制:偏向锁 → 轻量级锁 → 重量级锁。本文将带你深入理解三种锁的特点、实现原理与适用场景,彻底搞清楚锁升级的秘密。


正文

一、为什么需要锁升级?

并发场景下,锁竞争频率差异巨大

  • 有时几乎无竞争;
  • 有时存在少量竞争;
  • 还有时竞争激烈。

如果一上来就使用重量级锁(基于操作系统的互斥量),会导致频繁 用户态 ↔ 内核态切换 ,性能开销大。

因此 JVM 引入了 分级锁机制,根据竞争情况逐步升级。


二、锁的三种形态

1. 偏向锁(Biased Lock)
  • 设计初衷 :优化 无竞争场景
  • 特点:锁会"偏向"第一个获取它的线程,如果后续没有其他线程竞争,该线程再次进入时无需加锁。
  • 实现方式 :在对象头的 Mark Word 中记录线程 ID,下次判断时只需比对线程 ID。
  • 适用场景:大量单线程操作的场景,比如对象被一个线程反复使用。
java 复制代码
synchronized(lock) {
    // 偏向锁:几乎零开销
}
2. 轻量级锁(Lightweight Lock)
  • 设计初衷 :优化 少量竞争场景
  • 特点 :多个线程尝试获取锁时,使用 CAS(Compare-And-Swap) 操作在用户态竞争,不涉及内核。
  • 实现方式 :JVM 会在当前线程栈中创建一个 锁记录(Lock Record) ,并尝试用 CAS 将对象头的 Mark Word 指向它。
  • 适用场景:锁竞争不激烈,但可能有多个线程交替进入的情况。
3. 重量级锁(Heavyweight Lock)
  • 设计初衷 :解决 激烈竞争场景
  • 特点:多个线程竞争失败后进入阻塞状态,由操作系统调度,唤醒时需要内核态切换,开销大。
  • 实现方式:Mark Word 指向 Monitor 对象,失败线程进入 Monitor 的 EntryList 队列等待。
  • 适用场景:高并发、竞争激烈,无法避免阻塞。

三、锁的升级过程

锁状态在 JVM 中的演进顺序如下:

无锁 → 偏向锁 → 轻量级锁 → 重量级锁

  • 偏向锁 → 轻量级锁:当另一个线程加入竞争时,偏向锁会撤销并升级为轻量级锁。
  • 轻量级锁 → 重量级锁:当 CAS 自旋失败或竞争严重时,锁升级为重量级锁,线程进入阻塞。

⚠️ 注意:锁只能升级,不能降级(除非对象被回收再分配)。


四、直观对比

锁类型 开销大小 是否阻塞 适用场景
偏向锁 最小 单线程反复使用对象
轻量级锁 中等 否(自旋) 少量竞争
重量级锁 最大 高并发激烈竞争

五、工程实践中的建议

  1. 减少锁的持有时间
    缩小同步代码块范围,避免长时间持锁。
  2. 避免不必要的共享
    如果数据能做到线程私有,就不需要锁。
  3. 热点资源分片
    将一个全局锁拆分为多把锁,降低竞争概率。
  4. 替代方案
  • 在竞争激烈场景,考虑使用 ReentrantLock,它支持公平锁、可中断、超时等特性;
  • 在读多写少场景,可以用 ReadWriteLockStampedLock 提升并发度。

六、总结

  • 偏向锁:几乎零开销,适合无竞争场景;
  • 轻量级锁:利用 CAS 自旋,适合少量竞争;
  • 重量级锁:基于操作系统互斥量,适合激烈竞争;
  • 锁升级:无锁 → 偏向 → 轻量级 → 重量级,不会降级。
相关推荐
葫芦和十三7 小时前
图解 MongoDB 04|索引模型:每建一个索引,就是在 B+-tree 森林里多栽一棵
后端·mongodb·agent
用户47949283569159 小时前
claude Fable用不了?把Gpt 5.5pro接到你的claude code里
前端·后端
GetcharZp11 小时前
告别 Nginx 复杂配置!这款带 Web 面板的万能代理神器,让端口转发变得如此简单
后端
IT_陈寒13 小时前
React的useState居然还有这种坑?我差点删库跑路
前端·人工智能·后端
nanxun88614 小时前
记一次诡异的 Docker 容器"串包"故障排查
java
Pedantic14 小时前
SwiftUI 手势笔记
前端·后端
金銀銅鐵14 小时前
[Python] 从《千字文》中随机挑选汉字
后端·python
用户15630681035116 小时前
Day01 | Java 基础(Java SE)
java
飘尘17 小时前
前端转型全栈(Java后端)的快速上手指引
前端·后端·全栈