synchronized 做了哪些优化?
Java 中的 synchronized
关键字是保证线程安全的基本机制,随着 JVM 的发展,它经历了多次优化以提高性能。
1. 锁升级机制(锁膨胀)
JDK 1.6 引入了偏向锁→轻量级锁→重量级锁的升级机制,避免了一开始就使用重量级锁:
1.1 偏向锁(Biased Locking)
- 优化场景:只有一个线程访问同步块
- 实现:在对象头记录偏向线程ID
- 优势:几乎无同步开销
- 触发升级:当有其他线程尝试获取锁时
1.2 轻量级锁(Thin Lock)
- 优化场景:多线程交替访问但无竞争
- 实现:通过CAS操作和栈帧中的Lock Record实现
- 优势:避免操作系统层面的线程阻塞
- 触发升级:当自旋获取锁失败(默认自旋10次)
1.3 重量级锁(Heavyweight Lock)
- 场景:真正的高竞争情况
- 实现:通过操作系统的互斥量(mutex)实现
- 特点:线程会进入阻塞状态
2. 自适应自旋锁(Adaptive Spinning)
- 自旋次数不再固定,而是根据:
- 前一次在该锁上的自旋成功情况
- 锁拥有者的状态
- 如果上次自旋成功,则增加自旋次数
- 如果很少成功,则可能直接跳过自旋
3. 锁消除(Lock Elimination)
- 优化场景:JIT 编译器通过逃逸分析确定对象不会逃逸当前线程
- 效果:完全移除不必要的同步操作
- 示例:
public void method() {Object lock = new Object(); // 局部对象,不会逃逸synchronized(lock) { // 会被优化掉// do something} }
4. 锁粗化(Lock Coarsening)
- 优化场景:相邻的同步块使用同一个锁
- 效果:合并多个同步块为一个,减少锁的获取/释放次数
- 示例:
// 优化前 synchronized(lock) { do1(); } synchronized(lock) { do2(); }// 优化后 synchronized(lock) { do1();do2(); }
5. 其他优化
5.1 偏向锁延迟启用
- 默认情况下,JVM 在启动后4秒才启用偏向锁(通过
BiasedLockingStartupDelay
参数配置) - 避免启动阶段大量竞争导致的偏向锁撤销开销
5.2 批量重偏向(Bulk Rebias)
- 当一类锁对象被多个线程交替使用,但未真正竞争时
- JVM 会批量重置这些对象的偏向锁,而不是逐个撤销
5.3 批量撤销(Bulk Revoke)
- 当一类锁对象的偏向模式不再有效时
- JVM 会一次性撤销所有该类实例的偏向锁
性能对比(JDK 1.6+ vs 早期版本)
场景 | 早期版本 | JDK 1.6+ 优化后 |
---|---|---|
单线程访问 | 重量级锁开销 | 偏向锁零开销 |
低竞争交替访问 | 重量级锁开销 | 轻量级锁CAS操作 |
短时间高竞争 | 线程立即阻塞 | 自旋尝试获取 |
长时间高竞争 | 线程阻塞 | 最终仍会阻塞 |
最佳实践
- 减少同步范围:只在必要代码块加锁
- 降低锁粒度:使用多个细粒度锁而非一个大锁
- 避免锁嵌套:容易导致死锁
- 考虑替代方案:在适当场景使用
java.util.concurrent
包中的并发工具
这些优化使得 synchronized
在大多数场景下的性能已经接近或超过显式锁(如 ReentrantLock
),同时保持了更好的安全性和易用性。
你想要的面试技术资料我全都有:https://pan.q删掉汉子uark.cn/s/aa7f2473c65b