众所周知,让开发者简单轻松的编写保证线程安全的代码,一直是现代编程语言所追求的,Java 也不例外。Java 语言引入的 synchronized 关键字,无不彰显它在此方面的勃勃雄心。但理想丰满现实骨感,早期的 Java 版本里,对于此关键字的实现太过厚重,导致线程同步的性能远不如预期。Java HotSpot™ VM 经过多个版本的迭代,利用锁膨胀思想,尽量延迟使用重量级锁的手段来提升 synchronized 原语的性能。
对象存储
对象结构
-
对象
对象在内存中的结构 (64位 JVM) 对象头 object header 成员变量 object field 对齐/填充(可选) alignment/padding gap (optional) -
对象数组
对象数组在内存中的结构 (64位 JVM) 对象头 object header 数组元素字节序列 elements bytes 对齐/填充(可选) alignment/padding gap (optional)
由于对象在内存以 8 字节 为最小单位,单个对象占用内存字节不是 8 的倍数时,需要增加留空字节凑成倍数,此时留空的字节被称为 对象对齐(object alignment)。单个对象内部的成员变量所占字节不满 4 的倍数时,也需增加留空字节凑成倍数,此时的留空字节被称为 填充间隙(padding gap)。
对象头结构
-
对象
对象的 Object Hearder 结构 (64位 JVM) Mark Word (64 位) Klass Word(压缩 32位,不压缩 64位)1 对齐/填充(可选) alignment/padding gap (optional) -
对象数组
对象数组的 Object Hearder 结构 (64位 JVM) Mark Word (64 位) Klass Word(压缩 32 位,不压缩 64 位) 数组长度(32 位) 对齐/填充(可选) alignment/padding gap (optional)
Mark Word 结构
Mark Word,为运行时对象的标记字,字在 32 位系统中占用 32 bit,64 位操作系统占用 64 bit。其记录着对象运行时的数据,包括 identity_hashcode、GC 分代年龄、锁状态 等信息。以下引用自 JDK8 HotSpot 源码 markOop.hpp 中,关于 Mark Word 结构信息的描述:
// 32 bits:
// --------
// hash:25 ------------>| age:4 biased_lock:1 lock:2 (normal object)
// JavaThread*:23 epoch:2 age:4 biased_lock:1 lock:2 (biased object)
// size:32 ------------------------------------------>| (CMS free block)
// PromotedObject*:29 ---------->| promo_bits:3 ----->| (CMS promoted object)
//
// 64 bits:
// --------
// unused:25 hash:31 -->| unused:1 age:4 biased_lock:1 lock:2 (normal object)
// JavaThread*:54 epoch:2 unused:1 age:4 biased_lock:1 lock:2 (biased object)
// PromotedObject*:61 --------------------->| promo_bits:3 ----->| (CMS promoted object)
// size:64 ----------------------------------------------------->| (CMS free block)
//
// unused:25 hash:31 -->| cms_free:1 age:4 biased_lock:1 lock:2 (COOPs && normal object)
// JavaThread*:54 epoch:2 cms_free:1 age:4 biased_lock:1 lock:2 (COOPs && biased object)
// narrowOop:32 unused:24 cms_free:1 unused:4 promo_bits:3 ----->| (COOPs && CMS promoted object)
// unused:21 size:35 -->| cms_free:1 unused:7 ------------------>| (COOPs && CMS free block)
为了节省内存空间,64 位的 JVM 采取压缩普通对象指针 (COOPs,即 Compressed Ordinary Object Pointer) 2 技术,把对象指针由原来的 64 bit 压缩成 32 bit,进而省了一半的内存空间。
我们着重关注 64 位 JVM 的锁的几种状态,也就是通过上面的 biased_lock
、lock
两个标志位的排列组合得到:
biased_lock | lock | 状态 |
---|---|---|
0 | 01 | 无锁 normal object |
1 | 01 | 偏向锁 biased object |
0 | 00 | 轻量级锁 lightweight/thin object |
0 | 10 | 重量级锁 heavyweight/fat object |
0 | 11 | GC 标记 |
锁的种类
根据 Mark Word 中的锁状态,我们分别来介绍下。
重量级锁
利用系统级别的互斥量(mutex)实现同步临界区,由于系统级调用,开销大,故称其位重量级锁。
在下一节关于锁的状态改变图中,会发现一个 重量级监视器指针,由于它覆盖(官方称为 Displaced)了原本的 Mark Word,故它所指向的是复杂的数据结构 ObjectMonitor 就包含有用来存储备份的 Mark Word 信息。除此之外,该数据结构还包含锁的等待列表等信息,详细可参考 Monitor Object 设计模式。
- 优点:持有锁时间长、竞争激烈时,其他等待的线程会让出 CPU 进入等待列表
- 缺点:系统级调用,开销大
自旋锁
利用循环的方式来实现线程等待(忙等),等待期间内不让出 CPU 执行时间、免去系统级线程切换开销。
- 优点:避免线程切换
- 缺点:等待期间占用 CPU 资源
轻量级锁
采用原子性的 CAS 进行加解锁操作,加锁失败时自旋等待,成功则将 Mark Word 覆盖成指向线程栈中的 Lock Record 指针,此记录中同样含有原 Mark Word 记录的备份信息。CAS 调用不需系统级别调用,故称为轻量级锁。
- 优点:无需系统级调用,性能好于重量级锁
- 缺点:每次加解锁都需要一次 CAS 操作,采用自旋忙等,在竞争激烈、持有锁时间长会加重 CPU 负荷,会转到重量级锁。
偏向锁
给当前锁标记所属线程,使得所属线程进入同步临界区不用做任何特殊处理,只是简单的使用 CAS 操作将所属线程 ID 记录到 Mark Word 中,同一线程再次加解锁时无需 CAS 操作。
- 优点:只需初始化时进行一次 CAS 操作,之后加解锁都无需 CAS 操作
- 缺点:只能单线程无竞争时使用,一旦有其他线程就必须撤销转到轻量级。
锁状态转移
在给新建的对象分配内存时,其对象头信息会按照下图所示的进行分配,同时随着线程的竞争发送锁状态的转化:
状态转移步骤
具体锁转移的过程如下:
-
如果偏向锁机制是启用,那么新建的对象被初始化成匿名的偏向锁。之所以是匿名,是因为此时并没有具体偏向的线程 ID。
- 使用虚拟机参数
-XX:-UseBiasedLocking
可以关闭偏向锁,默认是启用的。 - 由于虚拟机参数
-XX:BiasedLockingStartupDelay
默认 4 秒,偏向锁机制会延迟 4 秒生效,测试时可以将其设置为 0 秒,防止偏向锁设置不生效。 - 调用对象默认的
hashCode()
、System.identityHashCode(obj)
方法会使偏向锁膨胀为轻量级锁 3。原因为生成的identity_hashcode
需要被记录到 Mark Word 中,而偏向锁结构没有设计存储备份 Mark Word 的指针,类似轻量级锁的lock record pointer
、重量级锁的heavyweight monitor pointer
。
- 使用虚拟机参数
- 当有单个线程尝试获取该对象锁时,将把此线程的 ID 写入 Mark Word,完成加锁操作。
- 当此单线程解锁时,一直不存在其他线程来竞争时,此时重偏向 (rebias) 匿名偏向锁,即无线程 ID 的偏向锁。
- 一旦有其他线程时(不管有无竞争),就撤销偏向 (revoke bias) 转为轻量级锁。
- 其他线程 CAS 操作将自己的线程号 ID 放入 Mark Word 会失败,此线程会执行撤销偏向,在原偏向锁持有线程到达安全点后暂停原线程,检查原线程是否还持有锁。如果已释放锁,将锁状态修改为普通无锁状态;如果未释放锁,拷贝 Mark Word 到原偏向锁线程的锁记录中,修改锁状态标志位为轻量级,把指向原偏向锁线程的锁记录的指针存入 Mark Word 中,唤醒原持有偏向锁线程。
- 原持有偏向锁线程继续从安全点之后运行,解锁时判断对象头的锁记录指针是否指向当前线程锁记录、且锁记录的备份 Mark Word 与现有对象头里的 Mark Word 一致,如果都一致说明没有其他线程等待此锁,如果不一致说明有其他线程等待,释放锁之后需要换线挂起的那些线程。
- 轻量级锁状态时,如果竞争激烈(等待线程多)、原线程持锁时间长(即其他线程自旋次数多、等待时间长)就会膨胀为重量级锁。
- 轻量级、重量级锁解锁后转为普通无锁状态,即后三位为
001
。 - 当然,在重量级锁状态时,如果竞争转为不激烈时,锁会降级为轻量级状态。
状态转移验证源代码
代码依赖
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.openjdk.jol</groupId>
<artifactId>jol-cli</artifactId>
<version>0.10</version>
</dependency>
</dependencies>
代码结构
project
└── src
│ └── test
│ │ └── java
│ │ │ └── org
│ │ │ │ └── reion
│ │ │ │ │ └── LockTest.java
│ └── main
│ │ └── resources
│ │ └── java
│ │ │ └── org
│ │ │ │ └── reion
│ │ │ │ │ └── Student.java
代码清单
-
Student.java
package org.reion; /** * 学生 */ public class Student { // 名 String firstName; // 姓 String lastName; }
-
LockTest.java
package org.reion; import org.junit.Before; import org.junit.Test; import java.util.Random; import org.openjdk.jol.info.ClassLayout; import static java.lang.System.out; public class OopTest { public static final String LINE_SEPARATOR = "\n<<============= %s ==============>>"; Student stu1; @Before public void before() { stu1 = new Student(); } // 具体测试方法在下面单个列出,此处省略 }
偏向锁
-
测试方法
/** * 该方法验证对象初始化后,由单个线程持有时的偏向锁形态。 */ @Test public void testBiasedLock() throws InterruptedException { // 确保代码运行时,已经启用偏向锁机制,有两种方式: // ① 偏向锁机制默认延迟 4 秒启动,故休眠 5 秒 Thread.sleep(5000); // stu1 之所以重新赋值,因为 before 里旧对象由于偏向锁延迟启动机制,生成不可偏向的对象 stu1 = new Student(); // ② 添加虚拟机参数,把默认延迟修改为 0 秒,虚拟机启动立马启用偏向锁机制 // -XX:BiasedLockingStartupDelay=0 //【未加锁,可偏向】 out.println(String.format(LINE_SEPARATOR, "未加锁,可偏向")); out.println(ClassLayout.parseInstance(stu1).toPrintable()); //【偏向锁】 synchronized (stu1) { out.println(String.format(LINE_SEPARATOR, "偏向锁")); out.println(ClassLayout.parseInstance(stu1).toPrintable()); } //【未加锁,可偏向】 out.println(String.format(LINE_SEPARATOR, "未加锁,可偏向")); out.println(ClassLayout.parseInstance(stu1).toPrintable()); }
-
输出结果
<<============= 未加锁,可偏向 ==============>> # WARNING: Unable to attach Serviceability Agent. You can try again with escalated privileges. Two options: a) use -Djol.tryWithSudo=true to try with sudo; b) echo 0 | sudo tee /proc/sys/kernel/yama/ptrace_scope org.reion.Student object internals: OFFSET SIZE TYPE DESCRIPTION VALUE 0 4 (object header) 05 00 00 00 (00000101 00000000 00000000 00000000) (5) 4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0) 8 4 (object header) b4 02 01 f8 (10110100 00000010 00000001 11111000) (-134151500) 12 4 java.lang.String Student.firstName null 16 4 java.lang.String Student.lastName null 20 4 (loss due to the next object alignment) Instance size: 24 bytes Space losses: 0 bytes internal + 4 bytes external = 4 bytes total <<============= 偏向锁 ==============>> org.reion.Student object internals: OFFSET SIZE TYPE DESCRIPTION VALUE 0 4 (object header) 05 a8 00 b9 (00000101 10101000 00000000 10111001) (-1191139323) 4 4 (object header) e1 7f 00 00 (11100001 01111111 00000000 00000000) (32737) 8 4 (object header) b4 02 01 f8 (10110100 00000010 00000001 11111000) (-134151500) 12 4 java.lang.String Student.firstName null 16 4 java.lang.String Student.lastName null 20 4 (loss due to the next object alignment) Instance size: 24 bytes Space losses: 0 bytes internal + 4 bytes external = 4 bytes total <<============= 未加锁,可偏向 ==============>> org.reion.Student object internals: OFFSET SIZE TYPE DESCRIPTION VALUE 0 4 (object header) 05 a8 00 b9 (00000101 10101000 00000000 10111001) (-1191139323) 4 4 (object header) e1 7f 00 00 (11100001 01111111 00000000 00000000) (32737) 8 4 (object header) b4 02 01 f8 (10110100 00000010 00000001 11111000) (-134151500) 12 4 java.lang.String Student.firstName null 16 4 java.lang.String Student.lastName null 20 4 (loss due to the next object alignment) Instance size: 24 bytes Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
注意: 由于 JVM 为 小端法(Little Endian) 故低字节在前,高字节在后。
因此:
打印结果中 object header 中 64 位 Mark Word 字节序列应该为
00 00 7f e1 b9 00 a8 05
轻量级锁
-
测试方法
/** * 该方法验证调用默认 hashCode 或 System.identityHashCode 时,变为轻量级锁的形态 */ @Test public void testThinLock() { // 调用 System.identityHashCode(stu1) 使对象 stu1 禁用偏向锁 // TIPS: // 让对象能够利用上偏向锁机制,应该重载对象的 hashCode 方法, // 避免 identityHashCode 方法调用,直接膨胀为轻量级锁 out.println(String.format(LINE_SEPARATOR, "System.identityHashCode 方法调用")); out.println(Integer.toHexString(stu1.hashCode())); //【未加锁,不可偏向】 out.println(String.format(LINE_SEPARATOR, "未加锁,不可偏向")); out.println(ClassLayout.parseInstance(stu1).toPrintable()); //【轻量级锁】 synchronized (stu1) { out.println(String.format(LINE_SEPARATOR, "轻量锁")); out.println(ClassLayout.parseInstance(stu1).toPrintable()); } //【未加锁,不可偏向】 out.println(String.format(LINE_SEPARATOR, "未加锁,不可偏向")); out.println(ClassLayout.parseInstance(stu1).toPrintable()); }
-
输出结果
<<============= System.identityHashCode 方法调用 ==============>> 6aa8ceb6 <<============= 未加锁,不可偏向 ==============>> # WARNING: Unable to attach Serviceability Agent. You can try again with escalated privileges. Two options: a) use -Djol.tryWithSudo=true to try with sudo; b) echo 0 | sudo tee /proc/sys/kernel/yama/ptrace_scope org.reion.Student object internals: OFFSET SIZE TYPE DESCRIPTION VALUE 0 4 (object header) 01 b6 ce a8 (00000001 10110110 11001110 10101000) (-1462847999) 4 4 (object header) 6a 00 00 00 (01101010 00000000 00000000 00000000) (106) 8 4 (object header) b4 02 01 f8 (10110100 00000010 00000001 11111000) (-134151500) 12 4 java.lang.String Student.firstName null 16 4 java.lang.String Student.lastName null 20 4 (loss due to the next object alignment) Instance size: 24 bytes Space losses: 0 bytes internal + 4 bytes external = 4 bytes total <<============= 轻量锁 ==============>> org.reion.Student object internals: OFFSET SIZE TYPE DESCRIPTION VALUE 0 4 (object header) 88 18 94 01 (10001000 00011000 10010100 00000001) (26482824) 4 4 (object header) 00 70 00 00 (00000000 01110000 00000000 00000000) (28672) 8 4 (object header) b4 02 01 f8 (10110100 00000010 00000001 11111000) (-134151500) 12 4 java.lang.String Student.firstName null 16 4 java.lang.String Student.lastName null 20 4 (loss due to the next object alignment) Instance size: 24 bytes Space losses: 0 bytes internal + 4 bytes external = 4 bytes total <<============= 未加锁,不可偏向 ==============>> org.reion.Student object internals: OFFSET SIZE TYPE DESCRIPTION VALUE 0 4 (object header) 01 b6 ce a8 (00000001 10110110 11001110 10101000) (-1462847999) 4 4 (object header) 6a 00 00 00 (01101010 00000000 00000000 00000000) (106) 8 4 (object header) b4 02 01 f8 (10110100 00000010 00000001 11111000) (-134151500) 12 4 java.lang.String Student.firstName null 16 4 java.lang.String Student.lastName null 20 4 (loss due to the next object alignment) Instance size: 24 bytes Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
偏向锁 -> 轻量级锁
-
测试方法
/** * 该方法验证第二个线程出现时(此时第二线程已经在第一个线程解锁后才执行,故无竞争),会撤销偏向,变为轻量级锁的形态。 */ @Test public void testBias2ThinLock() throws InterruptedException { // 调用偏向锁方法,生成可偏向的 stu1 对象 testBiasedLock(); // 生成新线程,申请对象 stu1 锁 out.println("当前线程:" + Thread.currentThread()); Runnable runnable = () -> { out.println("\n进入线程:" + Thread.currentThread()); out.println(String.format(LINE_SEPARATOR, "未请求锁,可偏向")); out.println(ClassLayout.parseInstance(stu1).toPrintable()); //【偏向锁 -膨胀-> 轻量级锁】 synchronized (stu1) { out.println(String.format(LINE_SEPARATOR, "已加锁,膨胀为轻量级锁")); out.println(ClassLayout.parseInstance(stu1).toPrintable()); } //【未加锁,不可偏向】 out.println(String.format(LINE_SEPARATOR, "未加锁,不可偏向")); out.println(ClassLayout.parseInstance(stu1).toPrintable()); out.println("\n退出线程:" + Thread.currentThread()); }; Thread thread = new Thread(runnable); thread.start(); thread.join(); }
-
输出结果
当前线程:Thread[main,5,main] 进入线程:Thread[Thread-1,5,main] <<============= 未请求锁,可偏向 ==============>> org.reion.Student object internals: OFFSET SIZE TYPE DESCRIPTION VALUE 0 4 (object header) 05 d0 00 be (00000101 11010000 00000000 10111110) (-1107243003) 4 4 (object header) fb 7f 00 00 (11111011 01111111 00000000 00000000) (32763) 8 4 (object header) b4 02 01 f8 (10110100 00000010 00000001 11111000) (-134151500) 12 4 java.lang.String Student.firstName null 16 4 java.lang.String Student.lastName null 20 4 (loss due to the next object alignment) Instance size: 24 bytes Space losses: 0 bytes internal + 4 bytes external = 4 bytes total <<============= 已加锁,膨胀为轻量级锁 ==============>> org.reion.Student object internals: OFFSET SIZE TYPE DESCRIPTION VALUE 0 4 (object header) 08 c9 3e 06 (00001000 11001001 00111110 00000110) (104777992) 4 4 (object header) 00 70 00 00 (00000000 01110000 00000000 00000000) (28672) 8 4 (object header) b4 02 01 f8 (10110100 00000010 00000001 11111000) (-134151500) 12 4 java.lang.String Student.firstName null 16 4 java.lang.String Student.lastName null 20 4 (loss due to the next object alignment) Instance size: 24 bytes Space losses: 0 bytes internal + 4 bytes external = 4 bytes total <<============= 未加锁,不可偏向 ==============>> org.reion.Student object internals: OFFSET SIZE TYPE DESCRIPTION VALUE 0 4 (object header) 01 00 00 00 (00000001 00000000 00000000 00000000) (1) 4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0) 8 4 (object header) b4 02 01 f8 (10110100 00000010 00000001 11111000) (-134151500) 12 4 java.lang.String Student.firstName null 16 4 java.lang.String Student.lastName null 20 4 (loss due to the next object alignment) Instance size: 24 bytes Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
轻量级锁维持
-
测试方法
/** * 该方法验证低竞争度时,轻量级锁能一直维持此状态,从而提高运行效率 */ @Test public void testLowContentionThinLock() throws InterruptedException { // 调用 testBias2ThinLock,使 stu1 经历 偏向锁 -> 轻量锁 过程 // 最终使 stu1 成为 未加锁不可偏 testBias2ThinLock(); Runnable runnable = () -> { Thread current = Thread.currentThread(); out.println("\n进入线程:" + current); //【轻量级锁】 for (int i = 0; i<2; i++) { try { // 400 毫秒内的随机休眠时间,模拟低对象锁竞争 Thread.sleep(new Random().nextInt(400)); } catch (InterruptedException e) { e.printStackTrace(); } synchronized (stu1) { out.println(String.format(LINE_SEPARATOR, current + " ROUND-" + i) + "\n" + ClassLayout.parseInstance(stu1).toPrintable()); } } out.println("\n退出线程:" + Thread.currentThread()); }; Thread t1 = new Thread(runnable); Thread t2 = new Thread(runnable); t1.start(); t2.start(); t1.join(); t2.join(); // 结束竞争后对象状态 out.println(String.format(LINE_SEPARATOR, Thread.currentThread()) + "\n" + ClassLayout.parseInstance(stu1).toPrintable()); }
-
输出结果
进入线程:Thread[Thread-2,5,main] 进入线程:Thread[Thread-3,5,main] <<============= Thread[Thread-3,5,main] ROUND-0 ==============>> org.reion.Student object internals: OFFSET SIZE TYPE DESCRIPTION VALUE 0 4 (object header) f8 98 1e 11 (11111000 10011000 00011110 00010001) (287217912) 4 4 (object header) 00 70 00 00 (00000000 01110000 00000000 00000000) (28672) 8 4 (object header) 83 02 01 f8 (10000011 00000010 00000001 11111000) (-134151549) 12 4 java.lang.String Student.firstName null 16 4 java.lang.String Student.lastName null 20 4 (loss due to the next object alignment) Instance size: 24 bytes Space losses: 0 bytes internal + 4 bytes external = 4 bytes total <<============= Thread[Thread-3,5,main] ROUND-1 ==============>> org.reion.Student object internals: OFFSET SIZE TYPE DESCRIPTION VALUE 0 4 (object header) f8 98 1e 11 (11111000 10011000 00011110 00010001) (287217912) 4 4 (object header) 00 70 00 00 (00000000 01110000 00000000 00000000) (28672) 8 4 (object header) 83 02 01 f8 (10000011 00000010 00000001 11111000) (-134151549) 12 4 java.lang.String Student.firstName null 16 4 java.lang.String Student.lastName null 20 4 (loss due to the next object alignment) Instance size: 24 bytes Space losses: 0 bytes internal + 4 bytes external = 4 bytes total 退出线程:Thread[Thread-3,5,main] <<============= Thread[Thread-2,5,main] ROUND-0 ==============>> org.reion.Student object internals: OFFSET SIZE TYPE DESCRIPTION VALUE 0 4 (object header) f8 68 0e 11 (11111000 01101000 00001110 00010001) (286157048) 4 4 (object header) 00 70 00 00 (00000000 01110000 00000000 00000000) (28672) 8 4 (object header) 83 02 01 f8 (10000011 00000010 00000001 11111000) (-134151549) 12 4 java.lang.String Student.firstName null 16 4 java.lang.String Student.lastName null 20 4 (loss due to the next object alignment) Instance size: 24 bytes Space losses: 0 bytes internal + 4 bytes external = 4 bytes total <<============= Thread[Thread-2,5,main] ROUND-1 ==============>> org.reion.Student object internals: OFFSET SIZE TYPE DESCRIPTION VALUE 0 4 (object header) f8 68 0e 11 (11111000 01101000 00001110 00010001) (286157048) 4 4 (object header) 00 70 00 00 (00000000 01110000 00000000 00000000) (28672) 8 4 (object header) 83 02 01 f8 (10000011 00000010 00000001 11111000) (-134151549) 12 4 java.lang.String Student.firstName null 16 4 java.lang.String Student.lastName null 20 4 (loss due to the next object alignment) Instance size: 24 bytes Space losses: 0 bytes internal + 4 bytes external = 4 bytes total 退出线程:Thread[Thread-2,5,main] <<============= Thread[main,5,main] ==============>> org.reion.Student object internals: OFFSET SIZE TYPE DESCRIPTION VALUE 0 4 (object header) 01 00 00 00 (00000001 00000000 00000000 00000000) (1) 4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0) 8 4 (object header) 83 02 01 f8 (10000011 00000010 00000001 11111000) (-134151549) 12 4 java.lang.String Student.firstName null 16 4 java.lang.String Student.lastName null 20 4 (loss due to the next object alignment) Instance size: 24 bytes Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
轻量级锁 -> 重量级锁 (高频型)
-
测试方法
/** * 该方法验证竞争变激烈(请求锁频率快)后,锁由轻量级膨胀为重量级的过程。 */ @Test public void testHighContentionThin2FatLock() throws InterruptedException { // 调用 testBias2ThinLock,使 stu1 经历 偏向锁 -> 轻量锁 过程 // 最终使 stu1 成为 未加锁不可偏 testBias2ThinLock(); Runnable runnable = () -> { Thread current = Thread.currentThread(); out.println("\n进入线程:" + current); //【轻量级锁】 for (int i = 0; i < 2; i++) { try { // 5 毫秒内的随机休眠时间,模拟高对象锁竞争 Thread.sleep(new Random().nextInt(5)); } catch (InterruptedException e) { e.printStackTrace(); } synchronized (stu1) { out.println(String.format(LINE_SEPARATOR, current + " ROUND-" + i) + "\n" + ClassLayout.parseInstance(stu1).toPrintable()); } } out.println("\n退出线程:" + Thread.currentThread()); }; Thread t1 = new Thread(runnable); Thread t2 = new Thread(runnable); t1.start(); t2.start(); t1.join(); t2.join(); // 结束竞争后对象状态 out.println(String.format(LINE_SEPARATOR, Thread.currentThread()) + "\n" + ClassLayout.parseInstance(stu1).toPrintable()); }
-
输出结果
<<============= 未加锁,不可偏向 ==============>> org.reion.Student object internals: OFFSET SIZE TYPE DESCRIPTION VALUE 0 4 (object header) 01 00 00 00 (00000001 00000000 00000000 00000000) (1) 4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0) 8 4 (object header) b4 02 01 f8 (10110100 00000010 00000001 11111000) (-134151500) 12 4 java.lang.String Student.firstName null 16 4 java.lang.String Student.lastName null 20 4 (loss due to the next object alignment) Instance size: 24 bytes Space losses: 0 bytes internal + 4 bytes external = 4 bytes total 退出线程:Thread[Thread-1,5,main] 进入线程:Thread[Thread-2,5,main] 进入线程:Thread[Thread-3,5,main] <<============= Thread[Thread-2,5,main] ROUND-0 ==============>> org.reion.Student object internals: OFFSET SIZE TYPE DESCRIPTION VALUE 0 4 (object header) da d3 02 e4 (11011010 11010011 00000010 11100100) (-469576742) 4 4 (object header) 9a 7f 00 00 (10011010 01111111 00000000 00000000) (32666) 8 4 (object header) b4 02 01 f8 (10110100 00000010 00000001 11111000) (-134151500) 12 4 java.lang.String Student.firstName null 16 4 java.lang.String Student.lastName null 20 4 (loss due to the next object alignment) Instance size: 24 bytes Space losses: 0 bytes internal + 4 bytes external = 4 bytes total <<============= Thread[Thread-3,5,main] ROUND-0 ==============>> org.reion.Student object internals: OFFSET SIZE TYPE DESCRIPTION VALUE 0 4 (object header) da d3 02 e4 (11011010 11010011 00000010 11100100) (-469576742) 4 4 (object header) 9a 7f 00 00 (10011010 01111111 00000000 00000000) (32666) 8 4 (object header) b4 02 01 f8 (10110100 00000010 00000001 11111000) (-134151500) 12 4 java.lang.String Student.firstName null 16 4 java.lang.String Student.lastName null 20 4 (loss due to the next object alignment) Instance size: 24 bytes Space losses: 0 bytes internal + 4 bytes external = 4 bytes total <<============= Thread[Thread-2,5,main] ROUND-1 ==============>> org.reion.Student object internals: OFFSET SIZE TYPE DESCRIPTION VALUE 0 4 (object header) da d3 02 e4 (11011010 11010011 00000010 11100100) (-469576742) 4 4 (object header) 9a 7f 00 00 (10011010 01111111 00000000 00000000) (32666) 8 4 (object header) b4 02 01 f8 (10110100 00000010 00000001 11111000) (-134151500) 12 4 java.lang.String Student.firstName null 16 4 java.lang.String Student.lastName null 20 4 (loss due to the next object alignment) Instance size: 24 bytes Space losses: 0 bytes internal + 4 bytes external = 4 bytes total 退出线程:Thread[Thread-2,5,main] <<============= Thread[Thread-3,5,main] ROUND-1 ==============>> org.reion.Student object internals: OFFSET SIZE TYPE DESCRIPTION VALUE 0 4 (object header) da d3 02 e4 (11011010 11010011 00000010 11100100) (-469576742) 4 4 (object header) 9a 7f 00 00 (10011010 01111111 00000000 00000000) (32666) 8 4 (object header) b4 02 01 f8 (10110100 00000010 00000001 11111000) (-134151500) 12 4 java.lang.String Student.firstName null 16 4 java.lang.String Student.lastName null 20 4 (loss due to the next object alignment) Instance size: 24 bytes Space losses: 0 bytes internal + 4 bytes external = 4 bytes total 退出线程:Thread[Thread-3,5,main] <<============= Thread[main,5,main] ==============>> org.reion.Student object internals: OFFSET SIZE TYPE DESCRIPTION VALUE 0 4 (object header) da d3 02 e4 (11011010 11010011 00000010 11100100) (-469576742) 4 4 (object header) 9a 7f 00 00 (10011010 01111111 00000000 00000000) (32666) 8 4 (object header) b4 02 01 f8 (10110100 00000010 00000001 11111000) (-134151500) 12 4 java.lang.String Student.firstName null 16 4 java.lang.String Student.lastName null 20 4 (loss due to the next object alignment) Instance size: 24 bytes Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
轻量级锁 -> 重量级锁 (长时型)
-
测试方法
/** * 该方法验证持锁时间长(等待线程自旋过多),锁由轻量级膨胀为重量级的过程。 */ @Test public void testLongContentionThin2FatLock() throws InterruptedException { // 调用 testBias2ThinLock,使 stu1 经历 偏向锁 -> 轻量锁 过程 // 最终使 stu1 成为 未加锁不可偏 testBias2ThinLock(); // 长期持有对象锁,在另一个线程开始竞争时,观察锁膨胀过程 Runnable runnable = () -> { Thread current = Thread.currentThread(); out.println("\n进入线程:" + current); //【轻量级锁】 synchronized (stu1) { int counter = 0; while (counter < 5) { counter++; try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } out.println(String.format(LINE_SEPARATOR, current + " 锁内") + "\n" + ClassLayout.parseInstance(stu1).toPrintable()); } } out.println("\n退出线程:" + current); }; // 睡眠 2.5 秒后,开始竞争锁 Runnable runnable2 = () -> { Thread current = Thread.currentThread(); out.println("\n进入线程:" + current); //【轻量级锁】 try { out.println(String.format(LINE_SEPARATOR, current + "睡眠...") + "\n\n"); Thread.sleep(2500); } catch (InterruptedException e) { e.printStackTrace(); } // 2.5 秒后参与竞争 out.println(String.format(LINE_SEPARATOR, current + "参与竞争...") + "\n\n"); synchronized (stu1) { out.println(String.format(LINE_SEPARATOR, current) + "\n" + ClassLayout.parseInstance(stu1).toPrintable()); } }; Thread t1 = new Thread(runnable); Thread t2 = new Thread(runnable2); t1.start(); t2.start(); t1.join(); t2.join(); }
-
输出结果
进入线程:Thread[Thread-2,5,main] 进入线程:Thread[Thread-3,5,main] <<============= Thread[Thread-3,5,main]睡眠... ==============>> <<============= Thread[Thread-2,5,main] 锁内 ==============>> org.reion.Student object internals: OFFSET SIZE TYPE DESCRIPTION VALUE 0 4 (object header) f0 98 a9 0c (11110000 10011000 10101001 00001100) (212441328) 4 4 (object header) 00 70 00 00 (00000000 01110000 00000000 00000000) (28672) 8 4 (object header) b4 02 01 f8 (10110100 00000010 00000001 11111000) (-134151500) 12 4 java.lang.String Student.firstName null 16 4 java.lang.String Student.lastName null 20 4 (loss due to the next object alignment) Instance size: 24 bytes Space losses: 0 bytes internal + 4 bytes external = 4 bytes total <<============= Thread[Thread-2,5,main] 锁内 ==============>> org.reion.Student object internals: OFFSET SIZE TYPE DESCRIPTION VALUE 0 4 (object header) f0 98 a9 0c (11110000 10011000 10101001 00001100) (212441328) 4 4 (object header) 00 70 00 00 (00000000 01110000 00000000 00000000) (28672) 8 4 (object header) b4 02 01 f8 (10110100 00000010 00000001 11111000) (-134151500) 12 4 java.lang.String Student.firstName null 16 4 java.lang.String Student.lastName null 20 4 (loss due to the next object alignment) Instance size: 24 bytes Space losses: 0 bytes internal + 4 bytes external = 4 bytes total <<============= Thread[Thread-3,5,main]参与竞争... ==============>> <<============= Thread[Thread-2,5,main] 锁内 ==============>> org.reion.Student object internals: OFFSET SIZE TYPE DESCRIPTION VALUE 0 4 (object header) 8a 3c 01 5d (10001010 00111100 00000001 01011101) (1560362122) 4 4 (object header) d2 7f 00 00 (11010010 01111111 00000000 00000000) (32722) 8 4 (object header) b4 02 01 f8 (10110100 00000010 00000001 11111000) (-134151500) 12 4 java.lang.String Student.firstName null 16 4 java.lang.String Student.lastName null 20 4 (loss due to the next object alignment) Instance size: 24 bytes Space losses: 0 bytes internal + 4 bytes external = 4 bytes total <<============= Thread[Thread-2,5,main] 锁内 ==============>> org.reion.Student object internals: OFFSET SIZE TYPE DESCRIPTION VALUE 0 4 (object header) 8a 3c 01 5d (10001010 00111100 00000001 01011101) (1560362122) 4 4 (object header) d2 7f 00 00 (11010010 01111111 00000000 00000000) (32722) 8 4 (object header) b4 02 01 f8 (10110100 00000010 00000001 11111000) (-134151500) 12 4 java.lang.String Student.firstName null 16 4 java.lang.String Student.lastName null 20 4 (loss due to the next object alignment) Instance size: 24 bytes Space losses: 0 bytes internal + 4 bytes external = 4 bytes total <<============= Thread[Thread-2,5,main] 锁内 ==============>> org.reion.Student object internals: OFFSET SIZE TYPE DESCRIPTION VALUE 0 4 (object header) 8a 3c 01 5d (10001010 00111100 00000001 01011101) (1560362122) 4 4 (object header) d2 7f 00 00 (11010010 01111111 00000000 00000000) (32722) 8 4 (object header) b4 02 01 f8 (10110100 00000010 00000001 11111000) (-134151500) 12 4 java.lang.String Student.firstName null 16 4 java.lang.String Student.lastName null 20 4 (loss due to the next object alignment) Instance size: 24 bytes Space losses: 0 bytes internal + 4 bytes external = 4 bytes total 退出线程:Thread[Thread-2,5,main] <<============= Thread[Thread-3,5,main] ==============>> org.reion.Student object internals: OFFSET SIZE TYPE DESCRIPTION VALUE 0 4 (object header) 8a 3c 01 5d (10001010 00111100 00000001 01011101) (1560362122) 4 4 (object header) d2 7f 00 00 (11010010 01111111 00000000 00000000) (32722) 8 4 (object header) b4 02 01 f8 (10110100 00000010 00000001 11111000) (-134151500) 12 4 java.lang.String Student.firstName null 16 4 java.lang.String Student.lastName null 20 4 (loss due to the next object alignment) Instance size: 24 bytes Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
轻量级锁 -> 重量级锁 -> 轻量级锁
-
测试方法
/** * 该方法验证竞争由强转弱后,锁由轻量级膨胀为重量级后又降级为轻量级的过程。 */ @Test public void testHighContentionThin2FatThenDeflate2ThinLock() throws InterruptedException { // 调用 testBias2ThinLock,使 stu1 经历 偏向锁 -> 轻量锁 过程 // 最终使 stu1 成为 未加锁不可偏 testBias2ThinLock(); Runnable runnable = () -> { Thread current = Thread.currentThread(); out.println("\n进入线程:" + current); // 随机产生 10 之内的循环追加数,模拟双线程中,一个线程完成后,剩下的线程单独执行时锁降级 int maxRound = new Random().nextInt(10); //【轻量级锁】 for (int i = 0; i < 5 + maxRound; i++) { try { // 5 毫秒内的随机休眠时间,模拟高对象锁竞争 Thread.sleep(new Random().nextInt(5)); } catch (InterruptedException e) { e.printStackTrace(); } synchronized (stu1) { out.println(String.format(LINE_SEPARATOR, current + " ROUND-" + i) + "\n" + ClassLayout.parseInstance(stu1).toPrintable()); } out.println(String.format(LINE_SEPARATOR, current + " ROUND-" + i) + "\n" + ClassLayout.parseInstance(stu1).toPrintable()); } out.println("\n退出线程:" + Thread.currentThread()); }; Thread t1 = new Thread(runnable); Thread t2 = new Thread(runnable); t1.start(); t2.start(); t1.join(); t2.join(); // 结束竞争后对象状态 out.println(String.format(LINE_SEPARATOR, Thread.currentThread()) + "\n" + ClassLayout.parseInstance(stu1).toPrintable()); }
-
输出结果
进入线程:Thread[Thread-2,5,main] 进入线程:Thread[Thread-3,5,main] <<============= Thread[Thread-2,5,main] ROUND-0 ==============>> org.reion.Student object internals: OFFSET SIZE TYPE DESCRIPTION VALUE 0 4 (object header) f0 a8 25 0f (11110000 10101000 00100101 00001111) (254126320) 4 4 (object header) 00 70 00 00 (00000000 01110000 00000000 00000000) (28672) 8 4 (object header) b4 02 01 f8 (10110100 00000010 00000001 11111000) (-134151500) 12 4 java.lang.String Student.firstName null 16 4 java.lang.String Student.lastName null 20 4 (loss due to the next object alignment) Instance size: 24 bytes Space losses: 0 bytes internal + 4 bytes external = 4 bytes total <<============= Thread[Thread-2,5,main] ROUND-0 ==============>> org.reion.Student object internals: OFFSET SIZE TYPE DESCRIPTION VALUE 0 4 (object header) 01 00 00 00 (00000001 00000000 00000000 00000000) (1) 4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0) 8 4 (object header) b4 02 01 f8 (10110100 00000010 00000001 11111000) (-134151500) 12 4 java.lang.String Student.firstName null 16 4 java.lang.String Student.lastName null 20 4 (loss due to the next object alignment) Instance size: 24 bytes Space losses: 0 bytes internal + 4 bytes external = 4 bytes total <<============= Thread[Thread-3,5,main] ROUND-0 ==============>> org.reion.Student object internals: OFFSET SIZE TYPE DESCRIPTION VALUE 0 4 (object header) f0 d8 35 0f (11110000 11011000 00110101 00001111) (255187184) 4 4 (object header) 00 70 00 00 (00000000 01110000 00000000 00000000) (28672) 8 4 (object header) b4 02 01 f8 (10110100 00000010 00000001 11111000) (-134151500) 12 4 java.lang.String Student.firstName null 16 4 java.lang.String Student.lastName null 20 4 (loss due to the next object alignment) Instance size: 24 bytes Space losses: 0 bytes internal + 4 bytes external = 4 bytes total <<============= Thread[Thread-3,5,main] ROUND-1 ==============>> org.reion.Student object internals: OFFSET SIZE TYPE DESCRIPTION VALUE 0 4 (object header) 5a d1 82 44 (01011010 11010001 10000010 01000100) (1149423962) 4 4 (object header) dd 7f 00 00 (11011101 01111111 00000000 00000000) (32733) 8 4 (object header) b4 02 01 f8 (10110100 00000010 00000001 11111000) (-134151500) 12 4 java.lang.String Student.firstName null 16 4 java.lang.String Student.lastName null 20 4 (loss due to the next object alignment) Instance size: 24 bytes Space losses: 0 bytes internal + 4 bytes external = 4 bytes total <<============= Thread[Thread-2,5,main] ROUND-1 ==============>> org.reion.Student object internals: OFFSET SIZE TYPE DESCRIPTION VALUE 0 4 (object header) 5a d1 82 44 (01011010 11010001 10000010 01000100) (1149423962) 4 4 (object header) dd 7f 00 00 (11011101 01111111 00000000 00000000) (32733) 8 4 (object header) b4 02 01 f8 (10110100 00000010 00000001 11111000) (-134151500) 12 4 java.lang.String Student.firstName null 16 4 java.lang.String Student.lastName null 20 4 (loss due to the next object alignment) Instance size: 24 bytes Space losses: 0 bytes internal + 4 bytes external = 4 bytes total 【【【【【【【【中间打印省略】】】】】】】】】 退出线程:Thread[Thread-3,5,main] <<============= Thread[Thread-2,5,main] ROUND-8 ==============>> org.reion.Student object internals: OFFSET SIZE TYPE DESCRIPTION VALUE 0 4 (object header) f0 a8 25 0f (11110000 10101000 00100101 00001111) (254126320) 4 4 (object header) 00 70 00 00 (00000000 01110000 00000000 00000000) (28672) 8 4 (object header) b4 02 01 f8 (10110100 00000010 00000001 11111000) (-134151500) 12 4 java.lang.String Student.firstName null 16 4 java.lang.String Student.lastName null 20 4 (loss due to the next object alignment) Instance size: 24 bytes Space losses: 0 bytes internal + 4 bytes external = 4 bytes total <<============= Thread[Thread-2,5,main] ROUND-8 ==============>> org.reion.Student object internals: OFFSET SIZE TYPE DESCRIPTION VALUE 0 4 (object header) 01 00 00 00 (00000001 00000000 00000000 00000000) (1) 4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0) 8 4 (object header) b4 02 01 f8 (10110100 00000010 00000001 11111000) (-134151500) 12 4 java.lang.String Student.firstName null 16 4 java.lang.String Student.lastName null 20 4 (loss due to the next object alignment) Instance size: 24 bytes Space losses: 0 bytes internal + 4 bytes external = 4 bytes total 退出线程:Thread[Thread-2,5,main]
参考资料
- Synchronization Created by Christian Wimmer, last modified on Apr 29, 2008
- Compressed Ordinary Object Pointer
- markOop.hpp
- What is in java object header
脚注
-
压缩的类指针 (Compressed Class Pointer),使用虚拟机参数
-XX:-UseCompressedClassPointers
关闭压缩,默认开启。 ↩ -
压缩的普通对象指针 (Compressed Ordinary Object Pointer),使用虚拟机参数
-XX:-UseCompressedOops
关闭压缩,默认开启。 ↩