浅析synchronized锁升级的原理与实现 2

news2024/9/23 3:15:13

本文内容是继我的上篇博客 浅析synchronized锁升级的原理与实现 1-CSDN博客

目录

 各状态锁的升级场景

无锁 --> 轻量级锁

偏向锁 --> 轻量级锁

偏向锁 --> 重量级锁

轻量级锁 --> 重量级锁

总结


 各状态锁的升级场景

下面我们结合代码看下各状态锁的升级场景。

需要添加JOL包,用来查看对象头信息。另外,采用JOL输出的对象头markword是16进制的,需要转换成64位的2进制来看。

<dependency>
    <groupId>org.openjdk.jol</groupId>
    <artifactId>jol-core</artifactId>
    <version>0.17</version>
</dependency>

无锁 --> 轻量级锁

无锁升级到轻量级锁有两种情况。

 1)第一种,关闭偏向锁,执行时增加JVM参数:-XX:-UseBiasedLocking。

public void lockUpgradeTest1() {
    Object obj = new Object();
    System.out.println("未开启偏向锁,对象信息");
    System.out.println(ClassLayout.parseInstance(obj).toPrintable());
    synchronized (obj) {
        System.out.println("已获取到锁信息");
        System.out.println(ClassLayout.parseInstance(obj).toPrintable());
    }
    System.out.println("已释放锁信息");
    System.out.println(ClassLayout.parseInstance(obj).toPrintable());
}

在这段代码中,首先创建了一个 Object 对象 obj。然后使用 ClassLayout.parseInstance(obj).toPrintable() 打印出该对象的内存布局。这段输出会显示对象在未加锁状态下的内存布局。在进入 synchronized 块时,线程获取了 obj 的锁。此时,会打印该对象在加锁状态下的内存布局。在 synchronized 块退出后,线程会释放锁,再次打印该对象的内存布局。

运行结果如下, 

未开启偏向锁,对象信息
java.lang.Object object internals:
OFF  SZ   TYPE DESCRIPTION               VALUE
  0   8        (object header: mark)     0x0000000000000001 (non-biasable; age: 0)
  8   4        (object header: class)    0xf80001e5
 12   4        (object alignment gap)    
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
 
已获取到锁信息
java.lang.Object object internals:
OFF  SZ   TYPE DESCRIPTION               VALUE
  0   8        (object header: mark)     0x000000000336f2b0 (thin lock: 0x000000000336f2b0)
  8   4        (object header: class)    0xf80001e5
 12   4        (object alignment gap)    
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
 
已释放锁信息
java.lang.Object object internals:
OFF  SZ   TYPE DESCRIPTION               VALUE
  0   8        (object header: mark)     0x0000000000000001 (non-biasable; age: 0)
  8   4        (object header: class)    0xf80001e5
 12   4        (object alignment gap)    
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

关闭偏向锁的情况下,对象加锁之前,对象头markword是0x0000000000000001换算成二进制末尾三位是001,即偏向锁标识为0,锁标识为01,是无锁状态。

加锁成功后,执行同步代码块,对象头markword是0x000000000336f2b0换算成二进制末尾两位是00,即锁标识为00,是轻量级锁状态。

最后,在执行完同步代码块后,再次打印对象头信息,对象头markword是0x0000000000000001换算成二进制末尾三位是001,即偏向锁标识为0,锁标识为01,是无锁状态,说明轻量级锁在执行完同步代码块后进行了锁的释放。

2) 第二种,默认情况下,在偏向锁延迟时间之前获取锁。

public void lockUpgradeTest2() {
    Object obj = new Object();
    System.out.println("开启偏向锁,偏向锁延迟时间前,对象信息");
    System.out.println(ClassLayout.parseInstance(obj).toPrintable());
    synchronized (obj) {
        System.out.println("已获取到锁信息");
        System.out.println(ClassLayout.parseInstance(obj).toPrintable());
    }
    System.out.println("开启偏向锁,已释放锁信息");
    System.out.println(ClassLayout.parseInstance(obj).toPrintable());
}

在这段代码中,首先创建了一个 Object 对象 obj。然后使用 ClassLayout.parseInstance(obj).toPrintable() 打印出该对象的内存布局。这段输出会显示对象在未加锁状态下的内存布局。在进入 synchronized 块时,线程获取了 obj 的锁。此时,会打印该对象在加锁状态下的内存布局。在 synchronized 块退出后,线程会释放锁,再次打印该对象的内存布局。

运行结果如下,

开启偏向锁,偏向锁延迟时间前,对象信息
java.lang.Object object internals:
OFF  SZ   TYPE DESCRIPTION               VALUE
  0   8        (object header: mark)     0x0000000000000001 (non-biasable; age: 0)
  8   4        (object header: class)    0xf80001e5
 12   4        (object alignment gap)    
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
 
已获取到锁信息
java.lang.Object object internals:
OFF  SZ   TYPE DESCRIPTION               VALUE
  0   8        (object header: mark)     0x000000000316f390 (thin lock: 0x000000000316f390)
  8   4        (object header: class)    0xf80001e5
 12   4        (object alignment gap)    
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
 
开启偏向锁,已释放锁信息
java.lang.Object object internals:
OFF  SZ   TYPE DESCRIPTION               VALUE
  0   8        (object header: mark)     0x0000000000000001 (non-biasable; age: 0)
  8   4        (object header: class)    0xf80001e5
 12   4        (object alignment gap)    
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

使用默认的偏向锁配置,JVM启动4秒后才启动偏向锁,所以JVM启动时就打印并获取锁信息,效果跟第一种一样,markword解释同上。

关闭偏向锁的情况下,对象加锁之前,对象头markword是0x0000000000000001换算成二进制末尾三位是001,即偏向锁标识为0,锁标识为01,是无锁状态。

加锁成功后,执行同步代码块,对象头markword是0x000000000316f390换算成二进制末尾两位也是00,即锁标识为00,是轻量级锁状态。

最后,在执行完同步代码块后,再次打印对象头信息,对象头markword是0x0000000000000001换算成二进制末尾三位是001,即偏向锁标识为0,锁标识为01,是无锁状态,说明轻量级锁在执行完同步代码块后进行了锁的释放。

偏向锁 --> 轻量级锁

public void lockUpgradeTest3() {
    // JVM默认4秒后才可以偏向锁,所以这里休眠5秒,锁对象就是偏向锁了
    try {
        Thread.sleep(5000);
    } catch (InterruptedException e) {
        throw new RuntimeException(e);
    }
    Object object = new Object();
    Thread t1 = new Thread(() -> {
        System.out.println(Thread.currentThread().getName() + "开启偏向锁,偏向锁延迟时间后,对象信息" + ClassLayout.parseInstance(object).toPrintable());
        synchronized (object) {
            System.out.println(Thread.currentThread().getName() + "已获取到锁信息" + ClassLayout.parseInstance(object).toPrintable());
        }
        System.out.println(Thread.currentThread().getName() + "开启偏向锁,已释放锁信息" + ClassLayout.parseInstance(object).toPrintable());
    }, "t1");
    t1.start();
    try {
        Thread.sleep(3000);
    } catch (InterruptedException e) {
        throw new RuntimeException(e);
    }
    Thread t2 = new Thread(() -> {
        System.out.println(Thread.currentThread().getName() + "开启偏向锁,偏向锁延迟时间后,对象信息" + ClassLayout.parseInstance(object).toPrintable());
        synchronized (object) {
            System.out.println(Thread.currentThread().getName() + "已获取到锁信息" + ClassLayout.parseInstance(object).toPrintable());
        }
        System.out.println(Thread.currentThread().getName() + "开启偏向锁,已释放锁信息" + ClassLayout.parseInstance(object).toPrintable());
    }, "t2");
    t2.start();
}

在 Java 中,偏向锁的默认启用有一定的延迟(通常是 4 秒),即 JVM 会在应用启动后的几秒钟内不启用偏向锁。通过 Thread.sleep(5000),确保程序等待 5 秒,以保证接下来创建的对象能够开启偏向锁。

然后,创建一个新的 Object 实例 object。这个对象将作为锁对象,供后续两个线程进行锁的获取和释放操作。

t1 线程在启动后,首先打印对象的内存布局信息,此时 object 应该已经启用了偏向锁。进入 synchronized (object) 代码块时,t1 线程获取到 object 的偏向锁,并打印锁定后的对象状态。退出 synchronized 代码块后,t1 释放锁,并再次打印对象的内存布局信息。

t1 线程启动后,程序再等待 3 秒,然后启动 t2 线程。t2 线程的执行逻辑与 t1 类似,但它在稍后启动,并且会检查在 t1 执行后 object 的锁状态。如果 t1 持有偏向锁且没有发生锁升级,t2 线程尝试获取锁时会触发偏向锁撤销,可能导致锁升级。

运行结果有两种可能:

1)第一种:

t1开启偏向锁,偏向锁延迟时间后,对象信息java.lang.Object object internals:
OFF  SZ   TYPE DESCRIPTION               VALUE
  0   8        (object header: mark)     0x0000000000000005 (biasable; age: 0)
  8   4        (object header: class)    0xf80001e5
 12   4        (object alignment gap)    
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
 
t1已获取到锁信息java.lang.Object object internals:
OFF  SZ   TYPE DESCRIPTION               VALUE
  0   8        (object header: mark)     0x000000001fbb3005 (biased: 0x000000000007eecc; epoch: 0; age: 0)
  8   4        (object header: class)    0xf80001e5
 12   4        (object alignment gap)    
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
 
t1开启偏向锁,已释放锁信息java.lang.Object object internals:
OFF  SZ   TYPE DESCRIPTION               VALUE
  0   8        (object header: mark)     0x000000001fbb3005 (biased: 0x000000000007eecc; epoch: 0; age: 0)
  8   4        (object header: class)    0xf80001e5
 12   4        (object alignment gap)    
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
 
t2开启偏向锁,偏向锁延迟时间后,对象信息java.lang.Object object internals:
OFF  SZ   TYPE DESCRIPTION               VALUE
  0   8        (object header: mark)     0x000000001fbb3005 (biased: 0x000000000007eecc; epoch: 0; age: 0)
  8   4        (object header: class)    0xf80001e5
 12   4        (object alignment gap)    
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
 
t2已获取到锁信息java.lang.Object object internals:
OFF  SZ   TYPE DESCRIPTION               VALUE
  0   8        (object header: mark)     0x0000000020f7f2d0 (thin lock: 0x0000000020f7f2d0)
  8   4        (object header: class)    0xf80001e5
 12   4        (object alignment gap)    
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
 
t2开启偏向锁,已释放锁信息java.lang.Object object internals:
OFF  SZ   TYPE DESCRIPTION               VALUE
  0   8        (object header: mark)     0x0000000000000001 (non-biasable; age: 0)
  8   4        (object header: class)    0xf80001e5
 12   4        (object alignment gap)    
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

启动JVM,默认4秒后开启偏向锁,这里休眠了5秒,保证JVM开启偏向锁,然后创建了对象,对象头markword信息0x0000000000000005换算成二进制后三位是101,偏向锁标识为1,锁标识为01,为偏向锁状态,偏向线程ID是0,说明这是初始偏向状态。

t1先获取到锁进入同步代码块后,markword变成0x000000001fbb3005转换成二进制为11111101110110011000000000101(前面补0直到长度是64位),末尾三位依然是101,还是偏向锁,只不过前54位将对应的操作系统线程ID写到偏向线程ID里了。

同步代码块执行完成后,markword依然没变,说明偏向锁状态不会自动释放锁,需要等其他线程来竞争锁才走偏向锁撤销流程。

t2线程开始执行时锁对象markword是0x000000001fbb3005,说明偏向锁偏向了t1对应的操作系统线程。

等t1释放锁,t2获取到锁进入同步代码块时,对象锁markword是0x0000000020f7f2d0,换算成二进制为100000111101111111001011010000(前面补0直到长度是64位),末尾两位是00,锁已经变成轻量级锁了,锁的指针也变了,是指向t2线程栈中的Lock Record记录了。

等t2线程释放锁后,对象锁末尾是001,说明是无锁状态了,轻量级锁会自动释放锁。

2)第二种

t1开启偏向锁,偏向锁延迟时间后,对象信息java.lang.Object object internals:
OFF  SZ   TYPE DESCRIPTION               VALUE
  0   8        (object header: mark)     0x0000000000000005 (biasable; age: 0)
  8   4        (object header: class)    0xf80001e5
 12   4        (object alignment gap)    
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
 
t1已获取到锁信息java.lang.Object object internals:
OFF  SZ   TYPE DESCRIPTION               VALUE
  0   8        (object header: mark)     0x000000002031d805 (biased: 0x0000000000080c76; epoch: 0; age: 0)
  8   4        (object header: class)    0xf80001e5
 12   4        (object alignment gap)    
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
 
t1开启偏向锁,已释放锁信息java.lang.Object object internals:
OFF  SZ   TYPE DESCRIPTION               VALUE
  0   8        (object header: mark)     0x000000002031d805 (biased: 0x0000000000080c76; epoch: 0; age: 0)
  8   4        (object header: class)    0xf80001e5
 12   4        (object alignment gap)    
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
 
t2开启偏向锁,偏向锁延迟时间后,对象信息java.lang.Object object internals:
OFF  SZ   TYPE DESCRIPTION               VALUE
  0   8        (object header: mark)     0x000000002031d805 (biased: 0x0000000000080c76; epoch: 0; age: 0)
  8   4        (object header: class)    0xf80001e5
 12   4        (object alignment gap)    
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
 
t2已获取到锁信息java.lang.Object object internals:
OFF  SZ   TYPE DESCRIPTION               VALUE
  0   8        (object header: mark)     0x000000002031d805 (biased: 0x0000000000080c76; epoch: 0; age: 0)
  8   4        (object header: class)    0xf80001e5
 12   4        (object alignment gap)    
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
 
t2开启偏向锁,已释放锁信息java.lang.Object object internals:
OFF  SZ   TYPE DESCRIPTION               VALUE
  0   8        (object header: mark)     0x000000002031d805 (biased: 0x0000000000080c76; epoch: 0; age: 0)
  8   4        (object header: class)    0xf80001e5
 12   4        (object alignment gap)    
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
 

t1线程正常获取锁,锁状态是偏向锁。

执行完同步代码块后锁还是偏向锁,说明偏向锁不随执行同步代码块的结束而释放锁。

t2线程拿到锁是偏向锁,获取到锁依然是偏向锁,而没有升级到轻量级锁,说明线程间锁没有竞争的情况下,依然保持偏向锁,这样效率会更高。

偏向锁 --> 重量级锁

public void lockUpgradeTest4() {
    // JVM默认4秒后才可以偏向锁,所以这里休眠5秒,锁对象就是偏向锁了
    try {
        Thread.sleep(5000);
    } catch (InterruptedException e) {
        throw new RuntimeException(e);
    }
    Object object = new Object();
    Thread t1 = new Thread(() -> {
        System.out.println(Thread.currentThread().getName() + "加锁前对象信息" + ClassLayout.parseInstance(object).toPrintable());
        synchronized (object) {
            System.out.println(Thread.currentThread().getName() + "已获取到锁信息" + ClassLayout.parseInstance(object).toPrintable());
            try {
                // 让t2线程启动后并竞争锁
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
        System.out.println(Thread.currentThread().getName() + "已释放锁信息" + ClassLayout.parseInstance(object).toPrintable());
    }, "t1");
    t1.start();
    try {
        // 让t1线程先启动并拿到锁
        Thread.sleep(3000);
    } catch (InterruptedException e) {
        throw new RuntimeException(e);
    }
    Thread t2 = new Thread(() -> {
        System.out.println(Thread.currentThread().getName() + "加锁前对象信息" + ClassLayout.parseInstance(object).toPrintable());
        synchronized (object) {
            System.out.println(Thread.currentThread().getName() + "已获取到锁信息" + ClassLayout.parseInstance(object).toPrintable());
        }
        System.out.println(Thread.currentThread().getName() + "已释放锁信息" + ClassLayout.parseInstance(object).toPrintable());
    }, "t2");
    t2.start();
}

在 Java 中,偏向锁默认有一个启用延迟(通常为4秒)。代码通过 Thread.sleep(5000),确保程序等待5秒钟后创建的 object 对象已经启用了偏向锁。

创建一个新的 Object 实例 object,该对象将用于后续线程的锁操作。

t1 线程首先打印 object 的内存布局信息,显示对象在未加锁状态下的布局(可能是偏向锁状态)。进入 synchronized (object) 块后,t1 获取到 object 的锁(此时锁可能为偏向锁),并再次打印对象的内存布局。t1 线程在持有锁的状态下,通过 Thread.sleep(3000) 让线程休眠3秒钟,模拟在锁内执行一些操作,同时为 t2 线程竞争锁创造条件。退出 synchronized 块后,t1 释放锁,并打印对象的内存布局信息。

在 t1 启动并持有锁3秒后,主线程通过 Thread.sleep(3000) 等待后再启动 t2,确保 t1 已经获取了 object 的锁。t2 线程的执行逻辑与 t1 类似,但由于 t1 持有锁,t2 必须等待 t1 释放锁后才能继续。t2 在获取到 object 的锁后,打印锁定前后的对象内存布局信息。t2 退出 synchronized 块后,释放锁并打印对象的内存布局信息。

运行结果如下所示,

t1加锁前对象信息java.lang.Object object internals:
OFF  SZ   TYPE DESCRIPTION               VALUE
  0   8        (object header: mark)     0x0000000000000005 (biasable; age: 0)
  8   4        (object header: class)    0xf80001e5
 12   4        (object alignment gap)    
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
 
t1已获取到锁信息java.lang.Object object internals:
OFF  SZ   TYPE DESCRIPTION               VALUE
  0   8        (object header: mark)     0x0000000020993805 (biased: 0x000000000008264e; epoch: 0; age: 0)
  8   4        (object header: class)    0xf80001e5
 12   4        (object alignment gap)    
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
 
t2加锁前对象信息java.lang.Object object internals:
OFF  SZ   TYPE DESCRIPTION               VALUE
  0   8        (object header: mark)     0x0000000020993805 (biased: 0x000000000008264e; epoch: 0; age: 0)
  8   4        (object header: class)    0xf80001e5
 12   4        (object alignment gap)    
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
 
t2已获取到锁信息java.lang.Object object internals:
OFF  SZ   TYPE DESCRIPTION               VALUE
  0   8        (object header: mark)     0x000000001d2c6c2a (fat lock: 0x000000001d2c6c2a)
  8   4        (object header: class)    0xf80001e5
 12   4        (object alignment gap)    
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
 
t1已释放锁信息java.lang.Object object internals:
OFF  SZ   TYPE DESCRIPTION               VALUE
  0   8        (object header: mark)     0x000000001d2c6c2a (fat lock: 0x000000001d2c6c2a)
  8   4        (object header: class)    0xf80001e5
 12   4        (object alignment gap)    
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
 
t2已释放锁信息java.lang.Object object internals:
OFF  SZ   TYPE DESCRIPTION               VALUE
  0   8        (object header: mark)     0x0000000000000001 (non-biasable; age: 0)
  8   4        (object header: class)    0xf80001e5
 12   4        (object alignment gap)    
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

程序先休眠5秒保证偏向锁开启,然后t1线程先启动并成功获取到锁,t1获取到锁之前对象markword是偏向状态但偏向线程ID是0,t1获取到锁之后markword里有了偏向线程ID,也就是t1线程对应的操作系统线程ID。

t2线程获取锁之前,对象锁已经是偏向锁并偏向t1对应的线程。

t2线程获取锁时t1已经持有锁并没有释放,锁未释放其他线程再竞争锁,这时会发生锁升级,由偏向锁升级成重量级锁,所以t1释放锁跟t2获取到锁时,对象头的markword是0x000000001d2c6c2a,转换成二进制11101001011000110110000101010(前后补0到够64位),最后两位是10,表示重量级锁,前面的62存的是指向堆中跟monitor对应锁对象的指针。

轻量级锁 --> 重量级锁

public void lockUpgradeTest5() {
        // JVM默认4秒后才可以偏向锁,所以这里休眠5秒,锁对象就是偏向锁了
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        Object object = new Object();
        Thread t1 = new Thread(() -> {
            System.out.println(Thread.currentThread().getName() + "加锁前对象信息" + ClassLayout.parseInstance(object).toPrintable());
            synchronized (object) {
                System.out.println(Thread.currentThread().getName() + "已获取到锁信息" + ClassLayout.parseInstance(object).toPrintable());
                try {
                    // 让t2线程启动后并竞争锁
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
            System.out.println(Thread.currentThread().getName() + "已释放锁信息" + ClassLayout.parseInstance(object).toPrintable());
        }, "t1");
        t1.start();
        try {
            // 让t1线程先启动并拿到锁
            t1.join();
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        Thread t2 = new Thread(() -> {
            System.out.println(Thread.currentThread().getName() + "加锁前对象信息" + ClassLayout.parseInstance(object).toPrintable());
            synchronized (object) {
                System.out.println(Thread.currentThread().getName() + "已获取到锁信息" + ClassLayout.parseInstance(object).toPrintable());
            }
            System.out.println(Thread.currentThread().getName() + "已释放锁信息" + ClassLayout.parseInstance(object).toPrintable());
        }, "t2");
        t2.start();
        for (int i = 0; i < 3; i++) {
            new Thread(() -> {
                System.out.println(Thread.currentThread().getName() + "加锁前对象信息" + ClassLayout.parseInstance(object).toPrintable());
                synchronized (object) {
                    System.out.println(Thread.currentThread().getName() + "已获取到锁信息" + ClassLayout.parseInstance(object).toPrintable());
                }
                System.out.println(Thread.currentThread().getName() + "已释放锁信息" + ClassLayout.parseInstance(object).toPrintable());
            }, "t3_" + i).start();
        }
 
    }

在 Java 中,偏向锁默认启用时存在延迟(通常为4秒),目的是避免在应用刚启动时频繁分配偏向锁。通过 Thread.sleep(5000) 确保应用等待5秒钟,从而偏向锁机制可以生效。

创建一个新的 Object 实例 object,这个对象将被多个线程用于同步锁的竞争。

t1 线程首先打印对象的内存布局,显示对象在未加锁状态下的布局(通常是偏向锁状态)。t1 获取对象锁后,进入 synchronized 块并打印加锁后的对象内存布局。t1 持有锁期间休眠3秒,以模拟锁内操作并确保其他线程(如 t2)在这段时间内尝试获取锁。退出同步块后,t1 释放锁,并打印对象的内存布局信息。

使用 t1.join() 确保 t2 在 t1 执行完毕后才启动,这样可以观察锁的释放与重新获取的过程。t2 的执行逻辑与 t1 类似,但由于 t1 已经对 object 进行了操作,锁状态可能已经发生了改变(如从偏向锁升级到轻量级锁)。t2 进入 synchronized 块后获取锁,并在释放锁时再次打印对象的内存布局信息。

启动三个 t3 线程 (t3_0t3_1t3_2),每个线程在运行时都尝试获取 object 的锁。这些线程的竞争行为可能导致锁的升级,特别是在多个线程快速尝试获取同一个对象锁的情况下,锁有可能从轻量级锁升级到重量级锁。每个 t3 线程在进入和退出同步块时都会打印对象的内存布局,显示锁状态的变化。

运行结果如下所示,

注意:这里t2线程也有可能获取到的锁是偏向锁,无竞争的情况下,这取决于线程的执行情况。这里我们以t2获取到轻量级锁,讲解轻量级锁升级到重量级锁的过程。

t1加锁前对象信息java.lang.Object object internals:
OFF  SZ   TYPE DESCRIPTION               VALUE
  0   8        (object header: mark)     0x0000000000000005 (biasable; age: 0)
  8   4        (object header: class)    0xf80001e5
 12   4        (object alignment gap)    
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
 
t1已获取到锁信息java.lang.Object object internals:
OFF  SZ   TYPE DESCRIPTION               VALUE
  0   8        (object header: mark)     0x0000000020e1b805 (biased: 0x000000000008386e; epoch: 0; age: 0)
  8   4        (object header: class)    0xf80001e5
 12   4        (object alignment gap)    
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
 
t1已释放锁信息java.lang.Object object internals:
OFF  SZ   TYPE DESCRIPTION               VALUE
  0   8        (object header: mark)     0x0000000020e1b805 (biased: 0x000000000008386e; epoch: 0; age: 0)
  8   4        (object header: class)    0xf80001e5
 12   4        (object alignment gap)    
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
 
t2加锁前对象信息java.lang.Object object internals:
OFF  SZ   TYPE DESCRIPTION               VALUE
  0   8        (object header: mark)     0x0000000020e1b805 (biased: 0x000000000008386e; epoch: 0; age: 0)
  8   4        (object header: class)    0xf80001e5
 12   4        (object alignment gap)    
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
 
t3_0加锁前对象信息java.lang.Object object internals:
OFF  SZ   TYPE DESCRIPTION               VALUE
  0   8        (object header: mark)     0x0000000020e1b805 (biased: 0x000000000008386e; epoch: 0; age: 0)
  8   4        (object header: class)    0xf80001e5
 12   4        (object alignment gap)    
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
 
t3_1加锁前对象信息java.lang.Object object internals:
OFF  SZ   TYPE DESCRIPTION               VALUE
  0   8        (object header: mark)     0x0000000020e1b805 (biased: 0x000000000008386e; epoch: 0; age: 0)
  8   4        (object header: class)    0xf80001e5
 12   4        (object alignment gap)    
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
 
t2已获取到锁信息java.lang.Object object internals:
OFF  SZ   TYPE DESCRIPTION               VALUE
  0   8        (object header: mark)     0x000000002270f1d0 (thin lock: 0x000000002270f1d0)
  8   4        (object header: class)    0xf80001e5
 12   4        (object alignment gap)    
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
 
t3_2加锁前对象信息java.lang.Object object internals:
OFF  SZ   TYPE DESCRIPTION               VALUE
  0   8        (object header: mark)     0x000000002270f1d0 (thin lock: 0x000000002270f1d0)
  8   4        (object header: class)    0xf80001e5
 12   4        (object alignment gap)    
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
 
t3_1已获取到锁信息java.lang.Object object internals:
OFF  SZ   TYPE DESCRIPTION               VALUE
  0   8        (object header: mark)     0x000000001d0356da (fat lock: 0x000000001d0356da)
  8   4        (object header: class)    0xf80001e5
 12   4        (object alignment gap)    
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
 
t2已释放锁信息java.lang.Object object internals:
OFF  SZ   TYPE DESCRIPTION               VALUE
  0   8        (object header: mark)     0x000000001d0356da (fat lock: 0x000000001d0356da)
  8   4        (object header: class)    0xf80001e5
 12   4        (object alignment gap)    
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
 
t3_1已释放锁信息java.lang.Object object internals:
OFF  SZ   TYPE DESCRIPTION               VALUE
  0   8        (object header: mark)     0x000000001d0356da (fat lock: 0x000000001d0356da)
  8   4        (object header: class)    0xf80001e5
 12   4        (object alignment gap)    
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
 
t3_0已获取到锁信息java.lang.Object object internals:
OFF  SZ   TYPE DESCRIPTION               VALUE
  0   8        (object header: mark)     0x000000001d0356da (fat lock: 0x000000001d0356da)
  8   4        (object header: class)    0xf80001e5
 12   4        (object alignment gap)    
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
 
t3_0已释放锁信息java.lang.Object object internals:
OFF  SZ   TYPE DESCRIPTION               VALUE
  0   8        (object header: mark)     0x000000001d0356da (fat lock: 0x000000001d0356da)
  8   4        (object header: class)    0xf80001e5
 12   4        (object alignment gap)    
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
 
t3_2已获取到锁信息java.lang.Object object internals:
OFF  SZ   TYPE DESCRIPTION               VALUE
  0   8        (object header: mark)     0x000000001d0356da (fat lock: 0x000000001d0356da)
  8   4        (object header: class)    0xf80001e5
 12   4        (object alignment gap)    
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
 
t3_2已释放锁信息java.lang.Object object internals:
OFF  SZ   TYPE DESCRIPTION               VALUE
  0   8        (object header: mark)     0x000000001d0356da (fat lock: 0x000000001d0356da)
  8   4        (object header: class)    0xf80001e5
 12   4        (object alignment gap)    
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
 

t1线程加锁执行代码块后,锁状态是偏向锁,t1在同步代码块里让休眠了3秒目的是让t2线程起来并竞争锁。

然后t1线程执行完同步代码块,锁状态还是偏向锁。

这时候for循环的3个线程也启动起来争抢锁,t2线程先启动获取到锁为轻量级锁,for循环里启动的3个线程在获取同步锁前,我们看到打印的锁状态有的是偏向锁、有的是轻量级锁,说明在t2线程加锁成功前还是偏向锁,t2加锁后就成轻量级锁了。

然后for循环的3个线程相继获取到锁,发现锁已经升级到重量级锁了,对象头markword是0x000000001d0356da,换成二进制:11101000000110101011011011010(前面补齐0到够64位),末尾两位锁状态是10,表示重量级锁。

总结

synchronized关键字是Java中常用的线程同步机制,其具备锁升级的特性,可以根据竞争的程度和锁的状态进行自动切换。锁升级通过无锁、偏向锁、轻量级锁和重量级锁四种状态的转换,以提高并发性能。在实际开发中,我们应该了解锁升级的原理,并根据具体场景进行合理的锁设计和优化,以实现高效且安全的多线程编程。

随着jdk版本的升级,JVM底层的实现持续优化,版本的不同伴随着参数使用及默认配置的不同,但总之JVM层对synchronized的优化效率越来越高,所以不应该再把synchronized同步当重量级锁来看。

其实本文介绍了锁升级的主要过程,关于synchronized还有锁消除、锁粗化的优化手段,使得synchronized性能在某些场景应用下,可能会比JUC包底下的Lock相关锁效率更高。
另外synchronized锁原理、优化、使用远不止本文说的这么多,感兴趣的可进一步探索。

转载自https://www.cnblogs.com/star95/p/17542850.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2095964.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

VL53L1CB TOF开发(2)----多区域扫描模式

VL53L1CB TOF开发.2--多区域扫描模式 概述视频教学样品申请源码下载硬件准备主要特点生成STM32CUBEMX串口配置IIC配置XSHUTGPIO1X-CUBE-TOF1堆栈设置函数说明初始化设置预设模式 (Preset mode)VL53L1_SetPresetModeVL53L1_SetDistanceMode时间预算单个ROI&#xff08;Single R…

从 Oracle 到 TiDB 丨数据库资源评估指南

原文来源&#xff1a; https://tidb.net/blog/5058e24f 本文作者&#xff1a;柳冬冬 导读 在当今技术飞速发展的时代&#xff0c;传统单机数据库正面临着前所未有的挑战。随着人工智能、云计算和大数据的崛起&#xff0c;企业对数据库的性能、可靠性和扩展性的需求日益增…

wordcloud兼figma的词云图片python生成

文章目录 一.Figma1.简介2.特点 二.代码构成1.详细代码2.word.py代码详解3.成果图 一.Figma 1.简介 Figma是一款全平台可使用的使用软件&#xff0c;和Sketch功能差不多&#xff1b;但是他可以在Mac&#xff0c;Windows PC&#xff0c;Linux计算机甚至Chromebook&#xff0c;…

中国各地级市-产业增加值、产业升级、产业结构高级化(2000-2021年)

产业增加值、产业升级和产业结构高级化是衡量地区经济发展水平的重要指标&#xff1a; 产业增加值&#xff1a;指在一定时期内&#xff0c;单位产值的增长部分&#xff0c;体现了产值、产量和增加值的综合增长能力。 产业升级&#xff1a;指通过技术进步和效率提升&#xff0c…

5.sklearn-朴素贝叶斯算法、决策树、随机森林

文章目录 环境配置&#xff08;必看&#xff09;头文件引用1.朴素贝叶斯算法代码运行结果优缺点 2.决策树代码运行结果决策树可视化图片优缺点 3.随机森林代码RandomForestClassifier()运行结果总结 环境配置&#xff08;必看&#xff09; Anaconda-创建虚拟环境的手把手教程相…

产品售后|基于SprinBoot+vue的产品售后管理​​​​​​​系统(源码+数据库+文档)

产品售后管理系统 目录 基于SprinBootvue的产品售后管理系统 一、前言 二、系统设计 三、系统功能设计 管理员模块实现 客户模块实现 受理人员模块实现 工程师模块实现 厂商模块实现 四、数据库设计 五、核心代码 六、论文参考 七、最新计算机毕设选题推荐 八、…

STM32外部中断(总结了易出现的BUG)

本文主要讲述了&#xff0c;本人在使用STM32F103C8T6做项目时&#xff0c;使用到按键触发外部中断时&#xff0c;发现无法触发外部中断。通过查看寄存器找出问题的过程&#xff0c;并总结了出现该问题的原因。 出现的问题 在使用STM32F103C8T6做一个矩阵键盘任务时&#xff0…

【学习笔记】卫星通信NTN 3GPP标准化进展分析(五)- 3GPP Release19 研究计划

一、引言&#xff1a; 本文来自3GPP Joern Krause, 3GPP MCC (May 14,2024) Non-Terrestrial Networks (NTN) (3gpp.org) 本文总结了NTN标准化进程以及后续的研究计划&#xff0c;是学习NTN协议的入门。 【学习笔记】卫星通信NTN 3GPP标准化进展分析&#xff08;一&#xff…

R语言报错 | object ‘integral‘ not found whilst loading name

1、报错背景 Registered S3 method overwritten by htmlwidgets:method from print.htmlwidget tools:rstudio Error: package or namespace load failed for ‘Seurat’:object integral not found whilst loading namespace spatstat.core 当我想library&…

RabbitMQ~架构、能力、AMQP、工作模式、高可用、死信队列了、事务机制了解

RabbitMQ RabbitMQ是使用Erlang编写的一个开源的消息中间件。它实现了AMQP(高级消息队列协议)&#xff0c;并支持其他消息传递协议&#xff1a;例如STOMP(简单文本定向消息协议)和MQTT(物联网协议)。 支持多种客户端如:Python、Ruby、.NET、Java、JMS、C、PHP、ActionScript、…

4-7 使用bios 中断 读取磁盘

1 首先是逻辑。 首先来看一下 他的编译代码的逻辑。 可以看到我 生成的 实际上是 Boot.bin , 这个文件可不止一个扇区&#xff0c; 然后将这个文件写入到了&#xff0c; disk1.img 这里加载了 disk1.img , disk2.img 我不太理解。 但是可以跑通&#xff0c; 暂时先不管了。…

How can I change from OpenAI to ChatOpenAI in langchain and Flask?

题意&#xff1a;“在 LangChain 和 Flask 中&#xff0c;如何将 OpenAI 更改为 ChatOpenAI&#xff1f;” 问题背景&#xff1a; This is an implementation based on langchain and flask and refers to an implementation to be able to stream responses from the OpenAI …

力扣763-划分字母区间(Java详细题解)

题目链接&#xff1a;763. 划分字母区间 - 力扣&#xff08;LeetCode&#xff09; 前情提要&#xff1a; 因为本人最近都来刷贪心类的题目所以该题就默认用贪心方法来做。 贪心方法&#xff1a;局部最优推出全局最优。 如果一个题你觉得可以用局部最优推出全局最优&#xf…

云服务器系统盘存储空间不够用怎么办?解决方法:扩容或挂载数据盘

云服务器系统盘满了不够用怎么办&#xff1f;服务器百科&#xff1a;可以系统盘扩容&#xff0c;也可以通过挂载数据盘来增大存储空间。 1、系统盘扩容教程&#xff1a;使用云服务器系统盘空间不足时&#xff0c;可以在ECS控制台上扩容云盘的容量以增加存储空间。阿里云支持云…

MCU3.电平等一些名词

1.电平的简单定义 计算机由各种硬件组成&#xff0c;只认识0和1&#xff0c;可以通过改变电压来向计算机输入数据&#xff08;0和1&#xff09; 例如&#xff1a;最大电压为3.3V 电压范围是0~3.3V&#xff0c;可以定义0~1V较低的电压表示0&#xff0c;定义2~3.3V较高的电压表…

领域驱动设计——大型结构(Large-Scale Structure)的综合运用

在一个大的、复杂的系统中&#xff0c;可能需要在一个设计中综合运用几种策略。那么&#xff0c;大型结构如何与CONTEXT MAP共存?应该把构造块放到哪里?第一步先做什么?第二步和第三步呢?如何设计你的战略? 把大型结构与BOUNDED CONTEXT结合起来使用 战略设计的3个基本原…

SpringBoot中@Value获取值和@ConfigurationProperties获取值用法及比较

SpringBoot中Value获取值和ConfigurationProperties获取值用法及比较 更新时间&#xff1a;2024年08月08日 09:41:48 作者&#xff1a;岳轩子 在Spring Boot中,Value注解是一个非常有用的特性,它允许我们将外部的配置注入到我们的Bean中,ConfigurationProperties用于将配置文件…

理解调试和组织 CSS——WEB开发系列26

CSS&#xff08;层叠样式表&#xff09;不仅是为网页提供样式的关键工具&#xff0c;也是调试和优化网页表现的重要部分。无论是调整网页布局&#xff0c;还是确保样式的一致性&#xff0c;掌握调试和组织 CSS 的技巧都是至关重要的。 一、使用浏览器开发者工具 浏览器开发者工…

【国外比较权威的免费的卫星数据网站——Sentinel Open Access Hub】

Sentinel Open Access Hub 网址&#xff1a;https://scihub.copernicus.eu/dhus/#/home简介&#xff1a;哨兵系列卫星科研数据中心&#xff08;Sentinel Open Access Hub&#xff09;是欧洲航天局&#xff08;ESA&#xff09;提供卫星数据的官方网站。该网站提供哨兵系列卫星的…

八、2 DMA数据转运 DMA函数介绍

把数组定义在Flash中&#xff0c;可以节省SRAM的空间 去掉const不会影响程序运行&#xff0c;但会占用SRAM的空间 1、步骤 &#xff08;1&#xff09;RCC开启DMAD的时钟 &#xff08;2&#xff09;调用DMA_Init&#xff0c;初始化参数 &#xff08;3&#xff09;调用DMA_Cmd…