摘要
在 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 自旋失败或竞争严重时,锁升级为重量级锁,线程进入阻塞。
⚠️ 注意:锁只能升级,不能降级(除非对象被回收再分配)。
四、直观对比
锁类型 | 开销大小 | 是否阻塞 | 适用场景 |
---|---|---|---|
偏向锁 | 最小 | 否 | 单线程反复使用对象 |
轻量级锁 | 中等 | 否(自旋) | 少量竞争 |
重量级锁 | 最大 | 是 | 高并发激烈竞争 |
五、工程实践中的建议
- 减少锁的持有时间
缩小同步代码块范围,避免长时间持锁。 - 避免不必要的共享
如果数据能做到线程私有,就不需要锁。 - 热点资源分片
将一个全局锁拆分为多把锁,降低竞争概率。 - 替代方案
- 在竞争激烈场景,考虑使用
ReentrantLock
,它支持公平锁、可中断、超时等特性; - 在读多写少场景,可以用
ReadWriteLock
或StampedLock
提升并发度。
六、总结
- 偏向锁:几乎零开销,适合无竞争场景;
- 轻量级锁:利用 CAS 自旋,适合少量竞争;
- 重量级锁:基于操作系统互斥量,适合激烈竞争;
- 锁升级:无锁 → 偏向 → 轻量级 → 重量级,不会降级。