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

本文内容是继我的上篇博客 浅析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

  • 7
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

水w

你的鼓励将是我创作的最大动力

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

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

打赏作者

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

抵扣说明:

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

余额充值