- Java锁的逻辑(结合对象头和ObjectMonitor)
- 还在用饼状图?来瞧瞧这些炫酷的百分比可视化新图形(附代码实现)⛵
- 自动注册实体类到EntityFrameworkCore上下文,并适配ABP及ABPVNext
- 基于Sklearn机器学习代码实战
在多线程编程中,线程同步是一个关键的概念,它确保了多个线程对共享资源的安全访问。Java中的synchronized关键字是一种常用的线程同步机制,它不仅提供了互斥访问的功能,还具备锁升级的特性。本文将深入探讨synchronized的锁升级原理和实现方式。 在jdk1.5(包含)版本之前,因为加锁和释放锁的过程JVM的底层都是由操作系统mutex lock来实现的,其中会涉及上下文的切换(即用户态和内核态的转换),性能消耗极其高,所以在当时synchronized锁是公认的重量级锁。 后来JVM开发团队为解决性能问题,在jdk1.5版本中加入了JUC并发包,包下开发了很多Lock相关的锁,来解决同步的性能问题,同时也开始在后续的迭代版本中对synchronized锁不断的进行优化来提高性能,比如在jdk1.6版本中就引入了“偏向锁”和“轻量级锁”,通过锁的升级来解决不同并发场景下的性能问题。 通常用使用synchronized方式加锁影响性能,主要原因如下:
需要储备的知识: java对象的内存布局 。
注意:本文代码所使用的JDK版本是1.8,JVM虚拟机是64位的HotSpot实现为准.
synchronized是java的同步关键字,可以使共享资源串行的执行,避免多线程竞争导致的执行结果错误,使用方法有以下三种.
public synchronized void lockInstance() {
System.out.println("锁的是当前对象实例");
}
public synchronized static void lockClass() {
System.out.println("锁的是当前类class");
}
public void lockObject(Object obj) {
synchronized (obj) {
System.out.println("锁的是指定的对象实例obj");
}
}
通过以上的用法,我们可以看到synchronized使用起来很简单,那它究竟是怎么做到线程间互斥访问的呢,底层原理及实现是怎样的呢,接下来我们一一解答。 前一篇文章写了 java对象的内存布局 ,里面有一个关于对象头Markword存储的内容表格,在synchronized锁的使用过程中就用到了,如下图所示.
在jdk1.5版本(包含)之前,锁的状态只有两种状态:“无锁状态”和“重量级锁状态”,只要有线程访问共享资源对象,则锁直接成为重量级锁,jdk1.6版本后,对synchronized锁进行了优化,新加了“偏向锁”和“轻量级锁”,用来减少上下文的切换以提高性能,所以锁就有了4种状态.
对于共享资源,不涉及多线程的竞争访问.
共享资源首次被访问时,JVM会对该共享资源对象做一些设置,比如将对象头中是否偏向锁标志位置为1,对象头中的线程ID设置为当前线程ID(注意:这里是操作系统的线程ID),后续当前线程再次访问这个共享资源时,会根据偏向锁标识跟线程ID进行比对是否相同,比对成功则直接获取到锁,进入 临界区域 (就是被锁保护,线程间只能串行访问的代码),这也是synchronized锁的可重入功能.
当多个线程同时申请共享资源锁的访问时,这就产生了竞争,JVM会先尝试使用轻量级锁,以CAS方式来获取锁(一般就是自旋加锁,不阻塞线程采用循环等待的方式),成功则获取到锁,状态为轻量级锁,失败(达到一定的自旋次数还未成功)则锁升级到重量级锁.
如果共享资源锁已经被某个线程持有,此时是偏向锁状态,未释放锁前,再有其他线程来竞争时,则会升级到重量级锁,另外轻量级锁状态多线程竞争锁时,也会升级到重量级锁,重量级锁由操作系统来实现,所以性能消耗相对较高。 这4种级别的锁,在获取时性能消耗:重量级锁 > 轻量级锁 > 偏向锁 > 无锁.
锁升级是针对于synchronized锁在不同竞争条件下的一种优化,根据锁在多线程中竞争的程度和状态,synchronized锁可在无锁、偏向锁、轻量级锁和重量级锁之间进行流转,以降低获取锁的成本,提高获取锁的性能。 通过下面这个命令,可以看到所有JVM参数的默认值.
java -XX:+PrintFlagsFinal -version
当一个共享资源首次被某个线程访问时,锁就会从无锁状态升级到偏向锁状态,偏向锁会在Markword的偏向线程ID里存储当前线程的操作系统线程ID,偏向锁标识位是1,锁标识位是01。此后如果当前线程再次进入临界区域时,只比较这个偏向线程ID即可,这种情况是在只有一个线程访问的情况下,不再需要操作系统的重量级锁来切换上下文,提供程序的访问效率。 另外需要注意的是,由于硬件资源的不断升级,获取锁的成本随之下降,jdk15版本后默认关闭了偏向锁。 如果未开启偏向锁(或者在JVM偏向锁延迟时间之前)有线程访问共享资源则直接由无锁升级为轻量级锁,请看第3步.
当第二个线程尝试获取偏向锁失败时,偏向锁会升级为轻量级锁,此时,JVM会使用CAS自旋操作来尝试获取锁,如果成功则进入临界区域,否则升级为重量级锁。 轻量级锁是在当前线程的栈帧中建立一个名为锁记录(Lock Record)的空间,尝试拷贝锁对象头的Markword到栈帧的Lock Record,若拷贝成功,JVM将使用CAS操作尝试将对象头的Markword更新为指向Lock Record的指针,并将Lock Record里的owner指针指向对象头的Markword。若拷贝失败,若当前只有一个等待线程,则可通过自旋继续尝试, 当自旋超过一定的次数,或者一个线程在持有锁,一个线程在自旋,又有第三个线程来访问时,轻量级锁就会膨胀为重量级锁.
锁升级简要步骤如下所示 。
注意:图中无锁到偏向锁这不是升级,是在偏向锁打开后,对象默认是偏向状态,没有从无锁升级到偏向锁的过程。偏向锁未开启,会直接从无锁升级到轻量级锁,偏向锁开启时,会从偏向锁升级到轻量级锁.
锁升级细化流程 下面我们结合代码看下各状态锁的升级场景 需要添加JOL包,用来查看对象头信息 。
<dependency>
<groupId>org.openjdk.jol</groupId>
<artifactId>jol-core</artifactId>
<version>0.17</version>
</dependency>
无锁升级到轻量级锁有两种情况 。
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());
}
运行结果:
未开启偏向锁,对象信息
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
采用JOL输出的对象头markword是16进制的,需要转换成64位的2进制来看.
关闭偏向锁的情况下,对象加锁之前,对象头markword是0x0000000000000001换算成二进制末尾三位是001,即偏向锁标识为0,锁标识为01,是无锁状态。 加锁成功后,执行同步代码块,对象头markword是0x000000000336f2b0换算成二进制末尾两位是00,即锁标识为00,是轻量级锁状态。 最后在执行完同步代码块后,再次打印对象头信息,对象头markword是0x0000000000000001换算成二进制末尾三位是001,即偏向锁标识为0,锁标识为01,是无锁状态,说明轻量级锁在执行完同步代码块后进行了锁的释放.
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());
}
运行结果:
开启偏向锁,偏向锁延迟时间前,对象信息
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解释同上.
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();
}
运行结果有两种可能: 第一种:
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,说明是无锁状态了,轻量级锁会自动释放锁。 第二种:
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();
}
运行结果:
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();
}
}
运行结果:
注意:这里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在代码层的用法有三种,锁对象实例、锁类class、锁指定实例对象,我们可以将以下代码编译成class后,在反编译出来看看JVM指令码是怎样的.
public class Synchronized1 {
public static void main(String[] args) {
System.out.println("test Synchronized1");
}
public synchronized void lockInstance() {
System.out.println("锁的是当前对象实例");
}
public synchronized static void lockClass() {
System.out.println("锁的是当前类class");
}
public void lockObject(Object obj) {
synchronized (obj) {
System.out.println("锁的是指定的对象实例obj");
}
}
}
通过javap命令反编译class文件。 我本文的例子使用的命令是这样的:
javap -c -v -l Synchronized1.class
我们主要关注那3个方法的JVM指令码.
在方法(非静态方法锁的是对象,静态方法锁的是类class)上加synchronized关键字,是通过在access_flags中设置ACC_SYNCHRONIZED标志来实现,synchronized使用在代码块上,是通过monitorenter和monitorexit指令来实现。 重量锁底层最终是依靠操作系统的阻塞和唤醒来实现,每个对象有一个监视器锁(monitor),在 Java 虚拟机(HotSpot)中,monitor 是基于 C++的ObjectMonitor实现,对象锁里有计数器、重入次数、等待锁的线程列表、存储该monitor的对象、拥有monitor的线程等参数,虚拟机是通过进入和退出monitor来实现同步,monitorenter指令是在编译后插入到同步代码块的开始位置,monitorexit是插入到方法结束处和异常处。根据虚拟机规范的要求,在执行monitorenter指令时,首先要去尝试获取对象的锁,如果这个对象没被锁定,或者当前线程已经拥有了那个对象的锁,把锁的计数器加1;相应地,在执行monitorexit指令时会将锁计数器减1,当计数器被减到0时,锁就释放了(注意执行monitorexit的线程必须是已经获得monitor对象锁的线程)。如果获取对象锁失败了,那当前线程就要阻塞等待,直到对象锁被另一个线程释放然后由操作系统唤醒等到锁的线程继续竞争锁直到获取到锁为止.
synchronized关键字是Java中常用的线程同步机制,其具备锁升级的特性,可以根据竞争的程度和锁的状态进行自动切换。锁升级通过无锁、偏向锁、轻量级锁和重量级锁四种状态的转换,以提高并发性能。在实际开发中,我们应该了解锁升级的原理,并根据具体场景进行合理的锁设计和优化,以实现高效且安全的多线程编程。 随着jdk版本的升级,JVM底层的实现持续优化,版本的不同伴随着参数使用及默认配置的不同,但总之JVM层对synchronized的优化效率越来越高,所以不应该再把synchronized同步当重量级锁来看。 其实本文介绍了锁升级的主要过程,关于synchronized还有锁消除、锁粗化的优化手段,使得synchronized性能在某些场景应用下,可能会比JUC包底下的Lock相关锁效率更高。 另外synchronized锁原理、优化、使用远不止本文说的这么多,感兴趣的可进一步探索.
最后此篇关于浅析synchronized锁升级的原理与实现的文章就讲到这里了,如果你想了解更多关于浅析synchronized锁升级的原理与实现的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。
我几乎不敢在这里问这个问题,因为它似乎应该很容易通过谷歌或 ravendb.net 获得。但是,我一直很难找到将我的 RavenDB 升级到新版本的正确方法。我目前正在运行 573 版并希望升级到 6
这周我需要升级当前版本的 DNN。我目前使用的是 2.1.1。我不想每件事都做两次,所以,我有几个问题。 是否有升级工具或某些脚本可以帮助我进行升级。 我最好安装 4.9 还是 5.0。这是生产。 如
将 Sugarcrm 从 6.2 升级到 6.3 版本时遇到问题。当我升级本地 Sugarcrm 安装时,它可以工作,但是当我开始升级我的 Sugarcrm 6.2 站点并上传升级包时,它不会上传。
有没有办法绕过 Meteor 的自动更新功能?我坚持 Downloading meteor-tool@1.3.0_3... \ 当我尝试运行现有项目,或创建一个新项目或只是运行“
我已将应用内集成到我的 Andorid 应用程序中,用于单个产品 productone。 为此,我在我的 Google Play 控制台中创建了不同的产品 ID,如下所示: 1。 productone
我在将 TeamCity 版本 2017.1.1 升级到 2017.1.2 时遇到问题。这个问题涉及 TeamCity 和 PostgreSQL 的工作。我的工作: 停止 teamcity 进程 /e
我寻找了这个问题的具体答案,但找不到——即使是在 WAMPSERVER 网站上也是如此。我确定我忽略了它。 我有 Wampserver 2.0、MySQL 5.0.51b、PHP 5.2.6 和 Ap
我使用 Ubuntu 软件中心默认的 Eclipse 3.7。 我想将 Eclipse 升级到 kepler 版本,所以我添加了 repository 我收到以下错误消息: Cannot comple
你好 我只想安装 mercurial,但对于它需要 python 2.6 的所有版本,我尝试使用 .rpm 文件,但我唯一得到的是很多充满错误的行,它告诉我:需要安装在 2.6 之前和 2.5 之后的
我完全知道 Gradle 网站上有一些页面说明了如何升级,但仅限于 4.x 及更高版本。 我正在尝试关注 tutorial制作一个简单的“我的第一个”Minecraft 模组。在其中,您被告知安装 f
我们想升级 Kerberos(服务器和客户端) 当前:1.6.3-133.27.1 目标:1.6.3-133.49.97.1 问题是如果我们用包管理器升级它,下面会发生什么? KDC 数据库 所有主要
背景 原计划 2019 年发布的 Vue3,又经过一年的再次打磨,终于于去年 9 月正式发布。随后,不少 UI 组件库都积极参与适配,去年 12 月,Element-plus(
我有一个版本为 2.3.4 的 grails 项目,我需要尽可能升级到最新版本。查看文档我意识到从 2.x 到 3.x 有巨大的变化。 问题是:从 2 到 3、从 3 到 4、从 4 到 5 逐步升级
我正在将 API 项目从 .net5 升级到 .net6 它以前工作,现在它崩溃 内部异常消息“抛出了‘Unity.Exceptions.InvalidRegistrationException’类型
我将我的项目从 expo 44 升级到 expo 45,现在我有无数这样的错误: The module 'MaterialIcons' can't be used as JSX component.
我已经升级了掌 Helm 模板(手动) 以前的片段depoloyment.yaml : apiVersion: apps/v1beta2 kind: Deployment metadata: na
我正在尝试将我的 Scala Play Framework 应用程序升级到 2.8,这涉及将 SBT 升级到 1.x。 在我的 build.propeties 我有 sbt.version=1.3.5
我想在我的 Windows 服务器上安装 PHPUnit 3.7。我遵循了各种说明 here并以 PHPUnit 3.4.1 结束。当我尝试使用以下方法再次安装它时: pear update chan
Microsoft.Net 4.5 即将推出,我想在 MS 发布最终版本时升级我的 clickonce 应用程序。 我的问题是:已经安装了 clickonce 应用程序(使用 .net 4.0)的用户
为了将 Angular 8 更新到 9,我正在按照官方文档升级。 这建议首先更新到最新版本的 angular 8,例如: ng update @angular/core@8 @angular/cli@
我是一名优秀的程序员,十分优秀!