JVM 锁膨胀机制:偏向锁到重量级锁的演进

目录

引言

锁状态概述

偏向锁

轻量级锁

重量级锁

锁膨胀机制的演进过程

偏向锁阶段

偏向锁撤销

轻量级锁阶段

轻量级锁升级为重量级锁

锁膨胀机制的代码示例

总结


引言

在 Java 并发编程中,锁是实现线程同步的重要手段。JVM 为了提高锁的性能,引入了多种锁状态,包括偏向锁、轻量级锁和重量级锁,并实现了锁膨胀机制,即锁会随着竞争情况的变化从偏向锁逐步升级为轻量级锁,最终可能升级为重量级锁。深入理解 JVM 锁膨胀机制对于优化 Java 并发程序的性能至关重要。

锁状态概述

偏向锁

 

偏向锁是为了在无竞争情况下减少锁的获取和释放开销而引入的。当一个线程第一次访问同步块并获取锁时,JVM 会在对象头中记录该线程的 ID,这个过程称为偏向锁的初始化。此后,该线程再次进入这个同步块时,无需进行任何同步操作,直接获取锁,从而提高了程序的执行效率。偏向锁适用于大多数情况下只有一个线程访问同步块的场景。

轻量级锁

当有另一个线程尝试竞争已经被偏向的锁时,偏向锁会被撤销,升级为轻量级锁。轻量级锁的设计目的是在没有多线程竞争的情况下,通过 CAS(Compare-And-Swap)操作来避免使用操作系统的互斥量,从而减少线程上下文切换的开销。线程在进入同步块时,会先在栈帧中创建一个锁记录(Lock Record),并尝试使用 CAS 操作将对象头中的 Mark Word 复制到锁记录中,同时将对象头的 Mark Word 指向锁记录。如果 CAS 操作成功,则该线程获取到轻量级锁;如果失败,则表示有其他线程已经获取了该锁,当前线程会尝试自旋等待锁的释放。

重量级锁

如果自旋等待一段时间后仍然无法获取到锁,轻量级锁会升级为重量级锁。重量级锁依赖于操作系统的互斥量(Mutex)来实现线程同步,当线程获取重量级锁失败时,会被阻塞并进入等待队列,直到锁被释放。重量级锁的开销较大,因为涉及到线程的上下文切换和操作系统的调度。

 

锁膨胀机制的演进过程

偏向锁阶段

当一个线程第一次访问同步块时,JVM 会将对象头中的 Mark Word 设置为偏向模式,并记录该线程的 ID。具体步骤如下:

 

  1. 检查对象头的 Mark Word 是否为偏向模式(Mark Word 的第 23 位为 1)。
  2. 如果是偏向模式,检查 Mark Word 中记录的线程 ID 是否与当前线程 ID 相同。
  3. 如果相同,则当前线程直接获取锁,无需进行任何同步操作。
  4. 如果不同,则表示该锁已经被其他线程偏向,需要撤销偏向锁。

偏向锁撤销

当有另一个线程尝试竞争已经被偏向的锁时,会触发偏向锁的撤销操作。撤销偏向锁的过程需要在全局安全点(Safe Point)进行,具体步骤如下:

  1. 暂停持有偏向锁的线程。
  2. 检查持有偏向锁的线程是否还在执行同步块。
  3. 如果已经退出同步块,则将对象头的 Mark Word 恢复为无锁状态,然后升级为轻量级锁。
  4. 如果还在执行同步块,则将偏向锁升级为轻量级锁,并将对象头的 Mark Word 指向锁记录。
  5. 恢复持有偏向锁的线程。

轻量级锁阶段

线程在进入同步块时,会先在栈帧中创建一个锁记录(Lock Record),并尝试使用 CAS 操作将对象头中的 Mark Word 复制到锁记录中,同时将对象头的 Mark Word 指向锁记录。具体步骤如下:

 

  1. 在当前线程的栈帧中创建一个锁记录(Lock Record)。
  2. 使用 CAS 操作将对象头的 Mark Word 复制到锁记录的 Owner 字段中。
  3. 如果 CAS 操作成功,则将对象头的 Mark Word 指向锁记录,当前线程获取到轻量级锁。
  4. 如果 CAS 操作失败,则表示有其他线程已经获取了该锁,当前线程会尝试自旋等待锁的释放。

轻量级锁升级为重量级锁

如果自旋等待一段时间后仍然无法获取到锁,轻量级锁会升级为重量级锁。升级过程如下:

 

  1. 线程在自旋一定次数后,仍然无法获取锁,会向操作系统申请一个互斥量(Mutex)。
  2. 将对象头的 Mark Word 设置为重量级锁的状态,指向互斥量的地址。
  3. 当前线程被阻塞并进入等待队列,直到锁被释放。

锁膨胀机制的代码示例

public class LockInflationExample {
    private static final Object lock = new Object();

    public static void main(String[] args) {
        // 线程 1 先获取锁
        Thread thread1 = new Thread(() -> {
            synchronized (lock) {
                System.out.println("Thread 1 acquired the lock.");
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });

        // 线程 2 稍后尝试获取锁
        Thread thread2 = new Thread(() -> {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (lock) {
                System.out.println("Thread 2 acquired the lock.");
            }
        });

        thread1.start();
        thread2.start();
    }
}

 

在上述代码中,线程 1 先获取锁并持有 2 秒钟,线程 2 在 1 秒后尝试获取锁。在这个过程中,可能会触发锁的膨胀机制,从偏向锁升级为轻量级锁,最终可能升级为重量级锁。

总结

JVM 锁膨胀机制通过根据竞争情况动态调整锁的状态,在不同的场景下选择最合适的锁类型,从而提高了锁的性能。偏向锁适用于无竞争的场景,轻量级锁适用于竞争较少的场景,重量级锁则适用于竞争激烈的场景。开发者在编写 Java 并发程序时,应该根据具体的业务场景合理使用锁,避免不必要的锁竞争,从而充分发挥 JVM 锁膨胀机制的优势,提高程序的性能。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

潜意识Java

源码一定要私信我,有问题直接问

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值