Java中的synchronized锁的四种状态与锁升级过程详解
字数 1761 2025-12-15 17:28:14
Java中的synchronized锁的四种状态与锁升级过程详解
题目描述:
在Java中,synchronized是最常用的同步机制,但其内部实现经历了从JDK 1.6开始的重大优化,引入了偏向锁、轻量级锁、重量级锁等状态,并实现了锁升级(膨胀)过程。本知识点要求深入理解synchronized锁的四种状态(无锁、偏向锁、轻量级锁、重量级锁)的特征、适用场景,以及锁如何根据竞争情况在这些状态间逐步升级(膨胀)的详细过程。
解题过程循序渐进讲解:
第一步:理解为什么需要锁状态优化
早期Java中,synchronized直接对应操作系统层面的互斥锁(重量级锁),涉及用户态到内核态的切换,性能开销大。为了提升性能,JDK 1.6引入了锁状态分级机制,根据实际竞争情况动态调整锁的级别,减少不必要的开销。
第二步:认识锁的四种状态
-
无锁(No Lock)
- 对象未被任何线程锁定,适用于无竞争场景。
- 对象的对象头(Object Header)中的标记字段(Mark Word)记录对象的哈希码、分代年龄等信息。
-
偏向锁(Biased Lock)
- 假设锁总是由同一线程获得,消除同步开销。
- 对象头存储偏向线程ID和偏向时间戳。
- 适用场景:单线程重复获取锁,无实际竞争。
-
轻量级锁(Lightweight Lock)
- 当多个线程交替执行同步块(无并发竞争)时,通过CAS操作竞争锁,避免阻塞。
- 对象头指向线程栈中的锁记录(Lock Record),实现自旋等待。
- 适用场景:线程交替执行,竞争轻微。
-
重量级锁(Heavyweight Lock)
- 传统互斥锁,通过操作系统互斥量(Mutex)实现,线程阻塞和唤醒涉及内核切换。
- 对象头指向监视器(Monitor)对象。
- 适用场景:高并发竞争,线程长时间等待。
第三步:深入锁升级(膨胀)过程
锁升级是单向过程:无锁 → 偏向锁 → 轻量级锁 → 重量级锁。以下逐步说明:
-
初始状态(无锁)
- 新创建的对象处于无锁状态。
- 对象头(Mark Word)包含哈希码、分代年龄等。
-
偏向锁获取
- 当线程A首次进入
synchronized块时,检查对象是否可偏向:- 检查对象头标记位(锁标志位和偏向锁标志位)。
- 如果可偏向(偏向锁标志=1且线程ID为空),则通过CAS将对象头中的线程ID设置为线程A的ID,并进入偏向模式。
- 如果对象已偏向线程A,则直接通过偏向锁检查,无需同步操作。
- 当线程A首次进入
-
偏向锁撤销
- 当线程B尝试获取锁时,发现对象已偏向线程A:
- 检查线程A是否存活或仍在同步块中。
- 如果线程A已不活动,则撤销偏向锁,恢复到无锁状态,并允许线程B重新偏向。
- 如果线程A仍在同步块中,则升级为轻量级锁。
- 当线程B尝试获取锁时,发现对象已偏向线程A:
-
轻量级锁获取
- 线程B通过CAS操作尝试将对象头中的指针替换为指向自己栈中锁记录的指针:
- 如果成功,则获取轻量级锁。
- 如果失败(锁已被占用),则自旋等待(有限次自旋,避免CPU浪费)。
- 自旋期间如果获得锁,则继续执行;如果自旋失败,则升级为重量级锁。
- 线程B通过CAS操作尝试将对象头中的指针替换为指向自己栈中锁记录的指针:
-
重量级锁膨胀
- 当多个线程竞争轻量级锁且自旋失败时,锁膨胀为重量级锁。
- 对象头指向监视器(Monitor)对象,线程进入阻塞队列,由操作系统调度。
- 此后,任何线程尝试获取锁时,都会进入阻塞状态,直到持有锁的线程释放锁后唤醒。
第四步:锁降级的特殊情况
- 正常情况下,锁升级是单向的,不会降级。
- 但在G1垃圾回收器的“并发标记”阶段,为了减少STW(Stop-The-World)时间,可能会对重量级锁进行降级,这是JVM内部的特殊处理,普通开发中无需关心。
第五步:实战中的调优建议
- 如果确认无竞争,可通过JVM参数
-XX:-UseBiasedLocking关闭偏向锁,避免撤销开销。 - 高并发场景下,偏向锁可能因频繁撤销而降低性能,可考虑直接使用轻量级锁或显式锁(如
ReentrantLock)。 - 通过JVM参数
-XX:PreBlockSpin调整自旋次数(默认10次),适配不同场景。
总结:
synchronized锁的四种状态与升级过程是JVM针对同步性能优化的核心机制。理解这一过程有助于编写高效并发代码,并在性能调优时做出合理决策。关键在于根据实际竞争情况,选择合适的锁级别,避免不必要的开销。