本文内容是继我的上篇博客 浅析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_0
, t3_1
, t3_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锁原理、优化、使用远不止本文说的这么多,感兴趣的可进一步探索。