摘要
synchronized
曾被认为是"重量级锁",性能不佳。但从 JDK1.6 起,经过偏向锁、轻量级锁、自适应自旋等优化,它的性能实现了质的飞跃。本文梳理了 synchronized 在 JDK1.6 到 JDK17 的演进过程,揭示性能优化背后的设计思想。
一、引言
早期 Java 开发者常常有一种刻板印象:
👉 "synchronized 很慢,要用 ReentrantLock 来替代。"
这种说法在 JDK1.5 以前的确成立,因为当时的 synchronized
直接依赖 操作系统重量级锁,用户态与内核态频繁切换,开销巨大。
但从 JDK1.6 开始,HotSpot JVM 对 synchronized
进行了深度优化,性能得到了显著提升。到 JDK17 ,synchronized
的效率已接近甚至媲美 ReentrantLock
。
接下来,我们按照版本时间线来回顾 synchronized 的性能演进之路。
二、JDK1.5 之前:重量级锁的年代
在 JDK1.5 及更早版本,synchronized
几乎没有优化:
- 直接依赖 ObjectMonitor → OS mutex。
- 线程竞争时立即进入 内核态阻塞,等待唤醒。
- 线程切换成本极高,导致吞吐量下降。
因此,那时业界普遍推荐 java.util.concurrent.Lock 作为更优选择。
三、JDK1.6:synchronized 大规模优化的起点
JDK1.6 可以说是 synchronized
的转折点。HotSpot 引入了多种锁优化机制:
1. 偏向锁(Biased Locking)
- 设计目标:消除无竞争场景下的锁开销。
- 实现方式:对象第一次被某线程获取锁时,Mark Word 记录该线程 ID。
- 后续相同线程再次进入 synchronized,直接通过 Mark Word 比对即可,无需 CAS。
👉 性能提升:在单线程场景下,synchronized 的成本几乎为零。
2. 轻量级锁(Lightweight Locking)
- 设计目标:优化短时间竞争的场景。
- 实现方式:通过 CAS 自旋尝试获取锁,避免立即进入阻塞。
- 如果 CAS 成功,线程直接进入临界区;失败则继续自旋一段时间。
👉 性能提升:避免频繁的内核态切换。
3. 自适应自旋锁(Adaptive Spinning)
- 自旋次数不再固定,而是 根据历史竞争情况动态调整。
- 如果过去自旋成功率高,则延长自旋时间;否则缩短甚至直接阻塞。
👉 性能提升:减少了无意义的 CPU 消耗。
总结: JDK1.6 让 synchronized 告别了"重量级"的标签,逐渐具备了与 ReentrantLock 竞争的实力。
四、JDK1.7:偏向锁与锁粗化
JDK1.7 在 JDK1.6 的基础上进一步优化:
1. 偏向锁延迟启用
- 在 JVM 启动初期,可能会有大量类加载、对象初始化,竞争概率较高。
- 因此 JDK1.7 默认会延迟几秒再启用偏向锁,以避免不必要的撤销成本。
2. 锁粗化(Lock Coarsening)
- JVM 会将 多个相邻的小同步块合并为一个大同步块。
- 目的是减少反复的加锁/解锁开销。
示例:
java
for (int i = 0; i < 100; i++) {
synchronized(this) {
// do something
}
}
在 JDK1.7 中,可能被优化为一次大的 synchronized 包裹整个循环。
五、JDK1.8:偏向锁默认开启
JDK1.8 中,偏向锁进一步普及:
- 默认启用偏向锁,而不是延迟几秒后才启用(可以通过参数调整)。
- 更加积极地使用偏向锁,适合大多数无锁竞争的业务场景。
同时,JVM 在锁撤销和升级的过程上也做了更高效的实现,降低了性能损耗。
六、JDK11:锁优化的稳定期
JDK11 属于 LTS 版本,许多企业的生产环境仍然在使用。
在这一阶段:
- 偏向锁 、轻量级锁 、重量级锁 三层机制非常稳定。
- synchronized 性能在大部分场景下已经接近 ReentrantLock。
- 官方并未再引入新的大规模优化,而是偏向稳定性和兼容性。
七、JDK15 与 JDK17:偏向锁被移除
这是 synchronized 性能演进的又一次重大变革。
1. JEP 374:Disable Biased Locking
- 从 JDK15 开始,偏向锁默认被禁用。
- 在 JDK17,偏向锁彻底被移除。
2. 原因分析
- 偏向锁虽然能优化无竞争场景,但在现代硬件和大多数应用场景下,实际收益有限。
- 其实现逻辑复杂,撤销成本较高。
- 维护偏向锁代码的收益 < 成本,因此被移除。
3. 影响
- JDK17 之后,锁优化主要依赖 轻量级锁(CAS + 自旋)和重量级锁。
- 实际性能几乎不受影响,因为现代 JVM 和 CPU 在无锁竞争场景下的 CAS 成本已经极低。
八、性能对比与现状
我们可以粗略总结 synchronized 在不同时期的性能表现:
JDK 版本 | 优化手段 | 性能表现 |
---|---|---|
JDK1.5 及之前 | 仅重量级锁 | 性能差,强依赖 OS 互斥量 |
JDK1.6 | 偏向锁、轻量级锁、自适应自旋 | 性能大幅提升,接近 Lock |
JDK1.7 | 偏向锁延迟启用、锁粗化 | 更智能,减少无效操作 |
JDK1.8 | 偏向锁默认开启 | 高并发下表现更优 |
JDK11 | 三层锁机制稳定 | 企业主力版本,性能成熟 |
JDK17 | 移除偏向锁,轻量级锁为主 | 性能无明显下降,更加简洁 |
九、总结
- JDK1.5 之前:synchronized 是重量级锁,性能不佳。
- JDK1.6:引入偏向锁、轻量级锁、自适应自旋,性能质变。
- JDK1.7 - JDK8:进一步优化锁延迟、锁粗化,进入高效稳定期。
- JDK15 - JDK17:偏向锁被移除,但整体性能不受影响。
👉 结论:现代 synchronized 已经非常高效,在大多数业务场景下无需刻意用 ReentrantLock 替代。