一、本质原因:CPU太快,内存太慢
现代计算机的CPU速度远超内存,导致多线程环境下数据同步成为难题:
- CPU计算速度:1条指令 ≈ 1纳秒
- 访问内存速度:≈ 100纳秒(比CPU慢100倍!)
- 多核CPU的缓存问题:每个核心有自己的缓存(L1/L2/L3),修改数据时,如何确保所有线程看到最新值?
锁的核心任务:
让多线程安全访问共享数据,本质是保证CPU缓存与主存的数据一致性。
但直接强制所有线程读主存(最严格的锁)会极大降低性能,于是JVM设计了锁升级,根据竞争情况动态调整锁策略,用最小的开销保证线程安全。另外我们经常使用的synchronized
的锁升级是 JVM 自动管理的,默认会经历多级升级。
二、通俗理解:锁升级就像游乐园的排队管理
1. 没人排队(无锁状态)
- 场景:游乐项目刚开放,没人玩。
- 管理方式:直接进去,无需任何检查。
- 开销:零。
2. 只有一个常客(偏向锁)
- 场景:每天都是同一个小孩来玩旋转木马。
- 管理方式:工作人员记住他的脸(记录线程ID),下次直接放行。
- 优点:极快,无需额外检查。
- 触发升级:当另一个小孩也来玩时(出现竞争)。
3. 几个小孩轮流玩(轻量级锁)
- 场景:3-4个小孩交替玩,不会同时抢。
- 管理方式:
- 放个签到本(栈帧中的锁记录)。
- 小孩来玩时签个名(CAS操作)。
- 如果发现别人在玩,就稍等一会儿(自旋)。
- 优点:无需叫保安,孩子们自己协调。
- 触发升级:多个小孩同时抢(自旋超过10次)。
4. 春游团来了(重量级锁)
- 场景:几十个小孩一窝蜂来抢。
- 管理方式:
- 叫保安维持秩序(操作系统介入)。
- 设立正式排队围栏(等待队列)。
- 必须严格排队,玩完一个才放下一个。
- 缺点:管理成本高(用户态→内核态切换)。
- 优点:绝对安全,不会混乱。
锁升级的核心思想:
根据竞争激烈程度,选择最合适的同步策略,避免“杀鸡用牛刀”。
三、详细技术解析:JVM的锁升级过程
1. 无锁 → 偏向锁(Biased Locking)
- 适用场景:单线程重复访问同步块。
- 实现方式:
- 对象头Mark Word记录线程ID。
- 同一线程再次进入时,直接检查线程ID,无需同步操作。
- 优点:几乎零开销。
- 触发撤销:当第二个线程尝试获取锁时。
2. 偏向锁 → 轻量级锁(Lightweight Locking)
- 适用场景:多线程交替访问,无激烈竞争。
- 实现方式:
- 线程在栈帧创建锁记录(Lock Record)。
- 使用CAS尝试将对象头指向锁记录。
- 失败则自旋(忙等待),避免线程阻塞。
- 优点:减少用户态→内核态切换。
- 触发升级:自旋超过阈值(默认10次)。
3. 轻量级锁 → 重量级锁(Heavyweight Locking)
- 适用场景:多线程激烈竞争。
- 实现方式:
- 对象头指向操作系统级互斥量(Mutex)。
- 未抢到锁的线程进入阻塞状态,由OS调度。
- 缺点:线程切换开销大(≈微秒级)。
- 优点:绝对安全,适合高并发场景。
四、锁升级的意义
1. 性能优化
- 无竞争时:偏向锁(接近无锁性能)。
- 低竞争时:轻量级锁(CAS自旋,避免线程切换)。
- 高竞争时:重量级锁(安全第一)。
2. 适应不同场景
- 单线程应用(如Android主线程):偏向锁最优。
- 低并发服务:轻量级锁平衡性能与安全。
- 高并发服务(如电商秒杀):重量级锁保证正确性。
3. 避免“一刀切”的锁策略
如果所有synchronized
都直接用重量级锁:
- 单线程场景下浪费性能。
- 低竞争时不必要的线程阻塞。
五、总结
- 锁升级的本质:
在“CPU缓存速度”和“内存一致性”之间找平衡,用最小开销保证线程安全。 - 锁升级的过程:
无锁 → 偏向锁 → 轻量级锁 → 重量级锁,竞争越激烈,锁越严格。 - 实际开发启示:
- 减少锁竞争(如缩小同步块)。
- 理解锁升级,避免过度优化(如盲目用
volatile
代替synchronized
)。
锁升级是JVM工程师精心设计的并发优化艺术,理解它,才能写出更高效的并发代码! 🚀