悠悠楠杉
Java并发包中锁升级过程的原理剖析,java锁升级机制
在多线程编程中,锁是保证线程安全的重要手段。Java从早期的synchronized
关键字到后来的ReentrantLock
,锁的实现机制不断优化。其中,锁升级(Lock Escalation)是JVM对synchronized
进行性能优化的核心策略,目的是减少锁竞争带来的开销。锁升级的过程分为三个阶段:偏向锁、轻量级锁和重量级锁。
1. 偏向锁:减少无竞争场景的开销
偏向锁(Biased Locking)是JDK 6引入的优化,适用于单线程重复访问同步块的场景。它的核心思想是:如果一段同步代码始终被同一个线程访问,JVM会通过CAS(Compare-And-Swap)操作将线程ID记录在对象头的Mark Word中,后续该线程进入同步块时无需额外加锁。
实现原理:
- 对象头中的Mark Word存储锁标志位和线程ID。
- 第一次加锁时,JVM使用CAS将线程ID写入Mark Word,并设置偏向锁标志(biased_lock:1
)。
- 后续同一线程进入同步块时,只需检查Mark Word中的线程ID是否匹配,无需CAS操作。
适用场景:单线程环境或低竞争场景。
2. 轻量级锁:应对短暂竞争
如果多个线程交替访问同步块(但未真正竞争),偏向锁会升级为轻量级锁(Lightweight Locking)。轻量级锁通过CAS和自旋(Spin)来避免直接进入阻塞状态。
实现原理:
- 线程在加锁时,JVM会在当前线程的栈帧中创建锁记录(Lock Record),并拷贝对象头的Mark Word到锁记录中。
- 然后尝试用CAS将Mark Word替换为指向锁记录的指针。如果成功,则获取锁;如果失败,说明存在竞争,进入自旋等待。
- 自旋超过一定次数(默认10次,可通过-XX:PreBlockSpin
调整)后,升级为重量级锁。
适用场景:低竞争、短时间同步的场景。
3. 重量级锁:高竞争下的最终方案
当多个线程激烈竞争同一锁时,轻量级锁会升级为重量级锁(Heavyweight Locking),此时JVM会调用操作系统层面的互斥量(Mutex)来管理线程阻塞和唤醒。
实现原理:
- 重量级锁依赖于Monitor对象(即管程),每个Java对象都与一个Monitor关联。
- 线程竞争锁时,会进入EntryList
队列;获取锁后,Monitor的owner
字段指向该线程。
- 未抢到锁的线程会被挂起(Park),进入WaitSet
队列,等待唤醒。
性能影响:
- 重量级锁涉及用户态到内核态的切换,开销较大。
- 但在高竞争场景下,自旋反而会浪费CPU资源,此时直接阻塞更高效。
4. 锁降级与优化
锁升级是单向的(偏向锁→轻量级锁→重量级锁),但JVM在某些情况下会撤销偏向锁(如调用hashCode()
方法导致Mark Word被占用)。此外,现代JVM还引入了适应性自旋(Adaptive Spinning),动态调整自旋次数。
总结
锁升级是JVM对synchronized
的优化策略,核心目标是:
1. 无竞争时:偏向锁减少CAS开销。
2. 低竞争时:轻量级锁通过自旋避免阻塞。
3. 高竞争时:重量级锁直接挂起线程。
理解锁升级过程有助于开发者在高并发场景下合理选择同步机制(如ReentrantLock
vs synchronized
),并优化JVM参数(如关闭偏向锁:-XX:-UseBiasedLocking
)。
思考:在分布式系统中,类似的“锁优化”思想是否适用?如何平衡性能与一致性?