Java中的synchronized锁的四种状态与锁升级过程详解
字数 1761 2025-12-15 17:28:14

Java中的synchronized锁的四种状态与锁升级过程详解

题目描述
在Java中,synchronized是最常用的同步机制,但其内部实现经历了从JDK 1.6开始的重大优化,引入了偏向锁、轻量级锁、重量级锁等状态,并实现了锁升级(膨胀)过程。本知识点要求深入理解synchronized锁的四种状态(无锁、偏向锁、轻量级锁、重量级锁)的特征、适用场景,以及锁如何根据竞争情况在这些状态间逐步升级(膨胀)的详细过程。


解题过程循序渐进讲解

第一步:理解为什么需要锁状态优化
早期Java中,synchronized直接对应操作系统层面的互斥锁(重量级锁),涉及用户态到内核态的切换,性能开销大。为了提升性能,JDK 1.6引入了锁状态分级机制,根据实际竞争情况动态调整锁的级别,减少不必要的开销。

第二步:认识锁的四种状态

  1. 无锁(No Lock)

    • 对象未被任何线程锁定,适用于无竞争场景。
    • 对象的对象头(Object Header)中的标记字段(Mark Word)记录对象的哈希码、分代年龄等信息。
  2. 偏向锁(Biased Lock)

    • 假设锁总是由同一线程获得,消除同步开销。
    • 对象头存储偏向线程ID和偏向时间戳。
    • 适用场景:单线程重复获取锁,无实际竞争。
  3. 轻量级锁(Lightweight Lock)

    • 当多个线程交替执行同步块(无并发竞争)时,通过CAS操作竞争锁,避免阻塞。
    • 对象头指向线程栈中的锁记录(Lock Record),实现自旋等待。
    • 适用场景:线程交替执行,竞争轻微。
  4. 重量级锁(Heavyweight Lock)

    • 传统互斥锁,通过操作系统互斥量(Mutex)实现,线程阻塞和唤醒涉及内核切换。
    • 对象头指向监视器(Monitor)对象。
    • 适用场景:高并发竞争,线程长时间等待。

第三步:深入锁升级(膨胀)过程
锁升级是单向过程:无锁 → 偏向锁 → 轻量级锁 → 重量级锁。以下逐步说明:

  1. 初始状态(无锁)

    • 新创建的对象处于无锁状态。
    • 对象头(Mark Word)包含哈希码、分代年龄等。
  2. 偏向锁获取

    • 当线程A首次进入synchronized块时,检查对象是否可偏向:
      • 检查对象头标记位(锁标志位和偏向锁标志位)。
      • 如果可偏向(偏向锁标志=1且线程ID为空),则通过CAS将对象头中的线程ID设置为线程A的ID,并进入偏向模式。
    • 如果对象已偏向线程A,则直接通过偏向锁检查,无需同步操作。
  3. 偏向锁撤销

    • 当线程B尝试获取锁时,发现对象已偏向线程A:
      • 检查线程A是否存活或仍在同步块中。
      • 如果线程A已不活动,则撤销偏向锁,恢复到无锁状态,并允许线程B重新偏向。
      • 如果线程A仍在同步块中,则升级为轻量级锁。
  4. 轻量级锁获取

    • 线程B通过CAS操作尝试将对象头中的指针替换为指向自己栈中锁记录的指针:
      • 如果成功,则获取轻量级锁。
      • 如果失败(锁已被占用),则自旋等待(有限次自旋,避免CPU浪费)。
    • 自旋期间如果获得锁,则继续执行;如果自旋失败,则升级为重量级锁。
  5. 重量级锁膨胀

    • 当多个线程竞争轻量级锁且自旋失败时,锁膨胀为重量级锁。
    • 对象头指向监视器(Monitor)对象,线程进入阻塞队列,由操作系统调度。
    • 此后,任何线程尝试获取锁时,都会进入阻塞状态,直到持有锁的线程释放锁后唤醒。

第四步:锁降级的特殊情况

  • 正常情况下,锁升级是单向的,不会降级。
  • 但在G1垃圾回收器的“并发标记”阶段,为了减少STW(Stop-The-World)时间,可能会对重量级锁进行降级,这是JVM内部的特殊处理,普通开发中无需关心。

第五步:实战中的调优建议

  • 如果确认无竞争,可通过JVM参数-XX:-UseBiasedLocking关闭偏向锁,避免撤销开销。
  • 高并发场景下,偏向锁可能因频繁撤销而降低性能,可考虑直接使用轻量级锁或显式锁(如ReentrantLock)。
  • 通过JVM参数-XX:PreBlockSpin调整自旋次数(默认10次),适配不同场景。

总结
synchronized锁的四种状态与升级过程是JVM针对同步性能优化的核心机制。理解这一过程有助于编写高效并发代码,并在性能调优时做出合理决策。关键在于根据实际竞争情况,选择合适的锁级别,避免不必要的开销。

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,则直接通过偏向锁检查,无需同步操作。 偏向锁撤销 当线程B尝试获取锁时,发现对象已偏向线程A: 检查线程A是否存活或仍在同步块中。 如果线程A已不活动,则撤销偏向锁,恢复到无锁状态,并允许线程B重新偏向。 如果线程A仍在同步块中,则升级为轻量级锁。 轻量级锁获取 线程B通过CAS操作尝试将对象头中的指针替换为指向自己栈中锁记录的指针: 如果成功,则获取轻量级锁。 如果失败(锁已被占用),则自旋等待(有限次自旋,避免CPU浪费)。 自旋期间如果获得锁,则继续执行;如果自旋失败,则升级为重量级锁。 重量级锁膨胀 当多个线程竞争轻量级锁且自旋失败时,锁膨胀为重量级锁。 对象头指向监视器(Monitor)对象,线程进入阻塞队列,由操作系统调度。 此后,任何线程尝试获取锁时,都会进入阻塞状态,直到持有锁的线程释放锁后唤醒。 第四步:锁降级的特殊情况 正常情况下,锁升级是单向的,不会降级。 但在G1垃圾回收器的“并发标记”阶段,为了减少STW(Stop-The-World)时间,可能会对重量级锁进行降级,这是JVM内部的特殊处理,普通开发中无需关心。 第五步:实战中的调优建议 如果确认无竞争,可通过JVM参数 -XX:-UseBiasedLocking 关闭偏向锁,避免撤销开销。 高并发场景下,偏向锁可能因频繁撤销而降低性能,可考虑直接使用轻量级锁或显式锁(如 ReentrantLock )。 通过JVM参数 -XX:PreBlockSpin 调整自旋次数(默认10次),适配不同场景。 总结 : synchronized 锁的四种状态与升级过程是JVM针对同步性能优化的核心机制。理解这一过程有助于编写高效并发代码,并在性能调优时做出合理决策。关键在于根据实际竞争情况,选择合适的锁级别,避免不必要的开销。