高级篇
https://wiki.openjdk.java.net/display/HotSpot/Synchronization (opens new window) https://stackoverflow.com/questions/26357186/what-is-in-java-object-header (opens new window) https://wiki.openjdk.java.net/display/lilliput/Main (opens new window) https://www.baeldung.com/java-memory-layout (opens new window) https://cloud.tencent.com/developer/article/1756611 (opens new window) https://shipilev.net/jvm/objects-inside-out/#_tools (opens new window) https://shipilev.net/jvm/objects-inside-out/#_introduction (opens new window) mark word 数据结构 (opens new window) JOL示例 (opens new window) JDK15 弃用偏向锁 (opens new window)
# 线程状态间切换
# Synchronized
# Java对象结构
Java对象(Object实例)结构包括三部分:对象头、对象体和对齐字节
# Java对象实例结构
- 对象头,对象头包括三个字段
- 第一个字段叫作Mark Word(标记字),用于存储自身运行时的数据,例如GC标志位、哈希码、锁状态等信息。
- 第二个字段叫作Class Pointer(类对象指针),用于存放方法区Class对象的地址,虚拟机通过这个指针来确定这个对象是哪个类的实例。
- 第三个字段叫作Array Length(数组长度)。如果对象是一个Java数组,那么此字段必须有,用于记录数组长度的数据;如果对象不是一个Java数组,那么此字段不存在,所以这是一个可选字段。
- 对象体
对象体包含对象的实例变量(成员变量),用于成员属性值,包括父类的成员属性值。这部分内存按4字节对齐。
- 对齐字节
对齐字节也叫作填充对齐,其作用是用来保证Java对象所占内存字节数为8的倍数HotSpotVM的内存管理要求对象起始地址必须是8字节的整数倍。对象头本身是8的倍数,当对象的实例变量数据不是8的倍数时,便需要填充数据来保证8字节的对齐。
# 对象结构中的字段长度
- Mark Word、Class Pointer、Array Length等字段的长度都与JVM的位数有关。Mark Word的长度为JVM的一个Word(字)大小,也就是说32位JVM的Mark Word为32位,64位JVM的Mark Word为64位。Class Pointer(类对象指针)字段的长度也为JVM的一个Word(字)大小,即32位JVM的Mark Word为32位,64位JVM的Mark Word为64位。
- 所以,在32位JVM虚拟机中,Mark Word和Class Pointer这两部分都是32位的;在64位JVM虚拟机中,Mark Word和Class Pointer这两部分都是64位的。
- 对于对象指针而言,如果JVM中的对象数量过多,使用64位的指针将浪费大量内存,通过简单统计,64位JVM将会比32位JVM多耗费50%的内存。为了节约内存可以使用选项
+UseCompressedOops
开 启 指 针 压 缩。 UseCompressedOops 中 的 Oop 为 Ordinary object pointer(普通对象指针)的缩写。 - 如果开启UseCompressedOops选项,以下类型的指针将从64位压缩至32位:
- Class对象的属性指针(静态变量)。
- Object对象的属性指针(成员变量)。
- 普通对象数组的元素指针。
当然,也不是所有的指针都会压缩,一些特殊类型的指针不会压缩,比如指向PermGen(永久代)的Class对象指针(JDK 8中指向元空间的Class对象指针)、本地变量、堆栈元素、入参、返回值和NULL指针等。
在堆内存小于32GB的情况下,64位虚拟机的UseCompressedOops选项是默认开启的,该选项表示开启Oop对象的指针压缩会将原来64位的Oop对象指针压缩为32位。 开启Oop对象指针压缩
-XX:+UseCompressedOops
关闭Oop对象指针压缩-XX:-UseCompressedOops
如果对象是一个数组,那么对象头还需要有额外的空间用于存储数组的长度(ArrayLength字段)。Array Length字段的长度也随着JVM架构的不同而不同:在32位JVM上,长度为32位;在64位JVM上,长度为64位。64位JVM如果开启了Oop对象的指针压缩,ArrayLength字段的长度也将由64位压缩至32位。
# Mark Word的结构信息
Java内置锁涉及很多重要信息,这些都存放在对象结构中,并且存放于对象头的MarkWord字段中。Mark Word的位长度为JVM的一个Word大小,也就是说32位JVM的Mark Word为32位,64位JVM为64位。Mark Word的位长度不会受到Oop对象指针压缩选项的影响。 Java内置锁的状态总共有4种,级别由低到高依次为:无锁、偏向锁、轻量级锁和重量级锁。其实在JDK 1.6之前,Java内置锁还是一个重量级锁,是一个效率比较低下的锁,在JDK1.6之后,JVM为了提高锁的获取与释放效率,对synchronized的实现进行了优化,引入了偏向锁和轻量级锁,从此以后Java内置锁的状态就有了4种(无锁、偏向锁、轻量级锁和重量级锁),并且4种状态会随着竞争的情况逐渐升级,而且是不可逆的过程,即不可降级,也就是说只能进行锁升级(从低级别到高级别)。
Java对象头里的Mark Word里默认存储对象的HashCode、分代年龄和锁标记位。32位JVM的Mark Word的默认存储结构 在运行期间,Mark Word里存储的数据会随着锁标志位的变化而变化。Mark Word可能变化为存储以下4种数据 在64位虚拟机下,Mark Word是64bit大小的
- lock:锁状态标记位,占两个二进制位,由于希望用尽可能少的二进制位表示尽可能多的信息,因此设置了lock标记。该标记的值不同,整个Mark Word表示的含义就不同。
- biased_lock:对象是否启用偏向锁标记,只占1个二进制位。为1时表示对象启用偏向锁,为0时表示对象没有偏向锁。lock和biased_lock两个标记位组合在一起共同表示Object实例处于什么样的锁状态
- age:4位的Java对象分代年龄。在GC中,对象在Survivor区复制一次,年龄就增加1。当对象达到设定的阈值时,将会晋升到老年代。默认情况下,并行GC的年龄阈值为15,并 发 GC 的 年 龄 阈 值 为 6。 由 于 age 只 有 4 位, 因 此 最 大 值 为 15, 这 就 是
-XX:MaxTenuringThreshold
选项最大值为15的原因。 - identity_hashcode:31位的对象标识HashCode(哈希码)采用延迟加载技术,当调用
Object.hashCode()
方法或者System.identityHashCode()
方法计算对象的HashCode后,其结果将被写到该对象头中。当对象被锁定时,该值会移动到Monitor(监视器)中。 - thread:54位的线程ID值为持有偏向锁的线程ID。
- epoch:偏向时间戳。
- ptr_to_lock_record:占62位,在轻量级锁的状态下指向栈帧中锁记录的指针。
- ptr_to_heavyweight_monitor:占62位,在重量级锁的状态下指向对象监视器的指针。
# 偏向锁
偏向锁使用的前提:
- 至少JDK1.6 版本且开启了偏向锁配置。
偏向锁在Java 6和Java 7里是默认启用的,但是它在应用程序启动几秒钟之后才激活,如有必要可以使用JVM参数来关闭延迟:
-XX:BiasedLockingStartupDelay=0
。如果你确定应用程序里所有的锁通常情况下处于竞争状态,可以通过JVM参数关闭偏向锁:-XX:-UseBiasedLocking=false
,那么程序默认会进入轻量级锁状态。 - 被加锁的对象,没有真正、或者隐式的调用父类 Object 里边的
hashcode
方法。
当一个线程访问同步块并获取锁时,会在对象头和栈帧中的锁记录里存储锁偏向的线程ID,以后该线程在进入和退出同步块时不需要进行CAS操作来加锁和解锁,只需简单地测试一下对象头的Mark Word里是否存储着指向当前线程的偏向锁。
- 如果测试成功,表示线程已经获得了锁。
- 如果测试失败,则需要再测试一下Mark Word中偏向锁的标识是否设置成1(表示当前是偏向锁):
- 如果没有设置,则使用CAS竞争锁;
- 如果设置了,则尝试使用CAS将对象头的偏向锁指向当前线程。
查看偏向锁默认配置
[root@localhost ~]# java -XX:+PrintFlagsInitial | grep Biased*
# 偏向锁重偏向阈值
intx BiasedLockingBulkRebiasThreshold = 20 {product}
# 偏向锁批量撤销阈值
intx BiasedLockingBulkRevokeThreshold = 40 {product}
intx BiasedLockingDecayTime = 25000 {product}
# 偏向锁延时时间
intx BiasedLockingStartupDelay = 4000 {product}
bool TraceBiasedLocking = false {product}
# 是否启用偏向锁
bool UseBiasedLocking = true {product}
示例代码
<dependency>
<groupId>org.openjdk.jol</groupId>
<artifactId>jol-core</artifactId>
<version>0.9</version>
</dependency>
package com.starry.sync;
import org.openjdk.jol.info.ClassLayout;
import org.openjdk.jol.vm.VM;
import java.util.HashMap;
/**
* @author starry
* @version 1.0
* @date 2022/5/29 17:52
* @Description 无锁、偏向锁、轻量级锁
*/
public class SyncBiasedLock {
/**
* @param args
*/
public static void main(String[] args) {
// test01 不进行任何配置
//test01();
// test02 配置立即激活偏向锁
// -XX:BiasedLockingStartupDelay=0 延迟激活偏向锁时间,不延迟
//test02();
// 调用hashcode方法,再进入sync
//test03();
// 在sync中调用hashcode方法
test04();
}
private static void test01() {
System.out.println(VM.current().details());
MyObject myObject = new MyObject();
System.out.println("*** 无锁 ***");
System.out.println(ClassLayout.parseInstance(myObject).toPrintable());
synchronized (myObject) {
System.out.println("*** 轻量级锁 ***");
System.out.println(ClassLayout.parseInstance(myObject).toPrintable());
}
}
private static void test02() {
System.out.println(VM.current().details());
MyObject myObject = new MyObject();
System.out.println("*** 未偏向任何线程的偏向锁 ***");
System.out.println(ClassLayout.parseInstance(myObject).toPrintable());
synchronized (myObject) {
System.out.println("*** 偏向锁 ***");
System.out.println(ClassLayout.parseInstance(myObject).toPrintable());
}
}
private static void test03() {
System.out.println(VM.current().details());
MyObject myObject = new MyObject();
System.out.println("*** 未偏向任何线程的偏向锁 ***");
System.out.println(ClassLayout.parseInstance(myObject).toPrintable());
// 显示调用hashcode方法
// myObject.hashCode();
// 隐式调用
new HashMap<>().put(myObject, null);
System.out.println("*** hashCode :" + Integer.toHexString(myObject.hashCode()));
System.out.println(ClassLayout.parseInstance(myObject).toPrintable());
synchronized (myObject) {
System.out.println("*** 轻量级锁 ***");
System.out.println(ClassLayout.parseInstance(myObject).toPrintable());
}
}
private static void test04() {
System.out.println(VM.current().details());
MyObject myObject = new MyObject();
System.out.println("*** 未偏向任何线程的偏向锁 ***");
System.out.println(ClassLayout.parseInstance(myObject).toPrintable());
synchronized (myObject) {
System.out.println("*** 轻量级锁 ***");
System.out.println(ClassLayout.parseInstance(myObject).toPrintable());
myObject.hashCode();
System.out.println("*** 轻量级锁状态获取hashcode ***");
System.out.println(ClassLayout.parseInstance(myObject).toPrintable());
}
}
static class MyObject {
// nothing
}
}
test01输出
# Running 64-bit HotSpot VM.
# Using compressed oop with 3-bit shift.
# Using compressed klass with 3-bit shift.
# Objects are 8 bytes aligned.
# Field sizes by type: 4, 1, 1, 2, 2, 4, 4, 8, 8 [bytes]
# Array element sizes: 4, 1, 1, 2, 2, 4, 4, 8, 8 [bytes]
*** 无锁 ***
com.starry.sync.SyncBiasedLock$MyObject 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 22 01 f8 (10000011 00100010 00000001 11111000) (-134143357)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
*** 轻量级锁 ***
com.starry.sync.SyncBiasedLock$MyObject object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) f8 f2 3f e5 (11111000 11110010 00111111 11100101) (-448793864)
4 4 (object header) de 00 00 00 (11011110 00000000 00000000 00000000) (222)
8 4 (object header) 83 22 01 f8 (10000011 00100010 00000001 11111000) (-134143357)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
不进行任何配置,会延迟激活偏向锁
- 第一次打印 00000001
0000:对象年龄(无锁和偏向锁时)
0:是否为偏向锁
01:锁标识
对象年龄暂时不做关心,这里第678位值: 0|01 就是无锁
对象头中的内容83 22 01 f8
为其Class Pointer(类对象指针),这里的长度为32位,是由于开启了指针压缩所导致的。从输出的结果也能看出,对oop(普通对象)、klass(类对象)指针都进行了压缩。Mark Word的位长度不会受到Oop对象指针压缩选项的影响。在堆内存小于32GB的情况下,64位虚拟机的UseCompressedOops
选项是默认开启的,该 选项表示开启Oop对象的指针压缩会将原来64位的Oop对象指针压缩为32位。
- 第二次打印 00111000
轻量级锁对象头只有锁标识和一个指针,指向拥有锁的线程的栈帧中的锁记录(lock record) 00:轻量级锁 因为执行时还没有激活偏向锁,所以直接成了轻量级锁
test02输出
# Running 64-bit HotSpot VM.
# Using compressed oop with 3-bit shift.
# Using compressed klass with 3-bit shift.
# Objects are 8 bytes aligned.
# Field sizes by type: 4, 1, 1, 2, 2, 4, 4, 8, 8 [bytes]
# Array element sizes: 4, 1, 1, 2, 2, 4, 4, 8, 8 [bytes]
*** 未偏向任何线程的偏向锁 ***
com.starry.sync.SyncBiasedLock$MyObject 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) 83 22 01 f8 (10000011 00100010 00000001 11111000) (-134143357)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
*** 偏向锁 ***
com.starry.sync.SyncBiasedLock$MyObject object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 05 d8 50 ea (00000101 11011000 01010000 11101010) (-363800571)
4 4 (object header) 99 01 00 00 (10011001 00000001 00000000 00000000) (409)
8 4 (object header) 83 22 01 f8 (10000011 00100010 00000001 11111000) (-134143357)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
配置了立即激活偏向锁
- 第一次打印 00000101
1:是偏向锁 01:偏向锁 00 00 00 00:后面应该存放线程 id,但是后面的 bit 都是0,说明未偏向任何线程 未偏向任何线程的偏向锁
- 第二次打印 00000101
1:是偏向锁 01:偏向锁 d8 50 ea 99 01 00 00:偏向的线程id(JVM内部的线程ID,和我们开发用的ID不一样,54bit)+时间戳(Epoch)合计为56位 1|01 为偏向锁
test03输出
# Running 64-bit HotSpot VM.
# Using compressed oop with 3-bit shift.
# Using compressed klass with 3-bit shift.
# Objects are 8 bytes aligned.
# Field sizes by type: 4, 1, 1, 2, 2, 4, 4, 8, 8 [bytes]
# Array element sizes: 4, 1, 1, 2, 2, 4, 4, 8, 8 [bytes]
*** 未偏向任何线程的偏向锁 ***
com.starry.sync.SyncBiasedLock$MyObject 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) 6e 22 01 f8 (01101110 00100010 00000001 11111000) (-134143378)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
*** hashCode :3581c5f3
com.starry.sync.SyncBiasedLock$MyObject object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 01 f3 c5 81 (00000001 11110011 11000101 10000001) (-2117733631)
4 4 (object header) 35 00 00 00 (00110101 00000000 00000000 00000000) (53)
8 4 (object header) 6e 22 01 f8 (01101110 00100010 00000001 11111000) (-134143378)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
*** 轻量级锁 ***
com.starry.sync.SyncBiasedLock$MyObject object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 68 f6 0f ec (01101000 11110110 00001111 11101100) (-334498200)
4 4 (object header) 2a 00 00 00 (00101010 00000000 00000000 00000000) (42)
8 4 (object header) 6e 22 01 f8 (01101110 00100010 00000001 11111000) (-134143378)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
- 第一次打印和之前一样:未偏向任何线程的偏向锁
- 第二次是为了查看hashcode:
3581c5f3
00000001:无锁
f3 c5 81 35:和3581c5f3
对不上,因为是用 little-endian (opens new window) 编写的标识哈希代码;在JVM中的数据使用大端模式存储和计算,而JOL工具使用小端模式进行输出
- 第三次打印 01101000:轻量级锁。对象一旦生成了哈希码,它就无法进入偏向锁状态。也就是说,只要一个对象已经计算过哈希码,它就无法进入偏向锁状态。
test04输出
# 省略前面的
*** 轻量级锁状态获取hashcode ***
com.starry.sync.SyncBiasedLock$MyObject object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) fa 63 d0 4e (11111010 01100011 11010000 01001110) (1322279930)
4 4 (object header) 43 02 00 00 (01000011 00000010 00000000 00000000) (579)
8 4 (object header) 83 22 01 f8 (10000011 00100010 00000001 11111000) (-134143357)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
Process finished with exit code 0
- 11111010:重量级锁
当一个对象当前正处于偏向锁状态,并且需要计算其哈希码的话,它的偏向锁会被撤销,并且锁会膨胀为重量级锁。
# 偏向锁释放
偏向锁使用了一种等到竞争出现才释放锁的机制,所以当其他线程尝试竞争偏向锁时,持有偏向锁的线程才会释放锁。偏向锁的撤销,需要等待全局安全点(在这个时间点上没有正在执行的字节码)。它会首先暂停拥有偏向锁的线程,然后检查持有偏向锁的线程是否活着,如果线程不处于活动状态,则将对象头设置成无锁状态;如果线程仍然活着,拥有偏向锁的栈会被执行,遍历偏向对象的锁记录,栈中的锁记录和对象头的Mark Word要么重新偏向于其他线程,要么恢复到无锁或者标记对象不适合作为偏向锁,最后唤醒暂停的线程。
package com.starry.sync;
import org.openjdk.jol.info.ClassLayout;
import java.util.concurrent.locks.LockSupport;
public class SyncLockRelease {
public static void main(String[] args) throws InterruptedException {
//test01();
test02();
//test03();
//test04();
}
/**
* threadA 加轻量级锁(未释放锁),threadB 争抢,升级重量级锁
* 等 threadA 和 threadB 都释放锁,再去加锁,此时是轻量级锁
*
* @throws InterruptedException
*/
private static void test04() throws InterruptedException {
Object obj = new Object();
new Thread(() -> {
synchronized (obj) {
System.out.println("AAA加锁中" + ClassLayout.parseInstance(obj).toPrintable());
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
Thread.sleep(3000);
new Thread(() -> {
synchronized (obj) {
System.out.println("BBB加锁中" + ClassLayout.parseInstance(obj).toPrintable());
}
}).start();
Thread.sleep(6000);
synchronized (obj) {
System.out.println("再次加锁" + ClassLayout.parseInstance(obj).toPrintable());
}
}
/**
* threadA 加锁,threadA 释放锁(线程存活),threadB 挣抢锁,持有轻量级锁
*
* @throws InterruptedException
*/
private static void test03() throws InterruptedException {
Object obj = new Object();
Thread threadA = new Thread(() -> {
System.out.println("AAA加锁前" + ClassLayout.parseInstance(obj).toPrintable());
synchronized (obj) {
System.out.println("AAA加锁中" + ClassLayout.parseInstance(obj).toPrintable());
}
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "AAA");
threadA.start();
Thread.sleep(500);
Thread threadB = new Thread(() -> {
System.out.println("BBB加锁前" + ClassLayout.parseInstance(obj).toPrintable());
synchronized (obj) {
System.out.println("BBB加锁中" + ClassLayout.parseInstance(obj).toPrintable());
}
}, "BBB");
threadB.start();
}
/**
* threadA 加锁,在threadA持有锁过程中(sync中),threadB 挣抢锁,直接升级为重量级锁
*
* @throws InterruptedException
*/
private static void test02() throws InterruptedException {
Object obj = new Object();
Thread threadA = new Thread(() -> {
System.out.println("AAA加锁前" + ClassLayout.parseInstance(obj).toPrintable());
synchronized (obj) {
try {
System.out.println("AAA加锁中" + ClassLayout.parseInstance(obj).toPrintable());
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "AAA");
threadA.start();
Thread.sleep(500);
Thread threadB = new Thread(() -> {
System.out.println("BBB加锁前" + ClassLayout.parseInstance(obj).toPrintable());
synchronized (obj) {
System.out.println("BBB加锁中" + ClassLayout.parseInstance(obj).toPrintable());
}
}, "BBB");
threadB.start();
}
/**
* threadA 加锁,等 threadA 执行完,threadB 再加锁
*
* @throws InterruptedException
*/
private static void test01() throws InterruptedException {
Object obj = new Object();
Thread threadA = new Thread(() -> {
System.out.println(Thread.currentThread().getName() + "加锁前");
System.out.println(ClassLayout.parseInstance(obj).toPrintable());
synchronized (obj) {
System.out.println(Thread.currentThread().getName() + "加锁中");
System.out.println(ClassLayout.parseInstance(obj).toPrintable());
}
System.out.println(Thread.currentThread().getName() + "加锁后");
System.out.println(ClassLayout.parseInstance(obj).toPrintable());
}, "AAA");
Thread threadB = new Thread(() -> {
LockSupport.park();
System.out.println(Thread.currentThread().getName() + "加锁前");
System.out.println(ClassLayout.parseInstance(obj).toPrintable());
synchronized (obj) {
System.out.println(Thread.currentThread().getName() + "加锁中");
System.out.println(ClassLayout.parseInstance(obj).toPrintable());
}
System.out.println(Thread.currentThread().getName() + "加锁后");
System.out.println(ClassLayout.parseInstance(obj).toPrintable());
}, "BBB");
threadA.start();
threadB.start();
threadA.join();
LockSupport.unpark(threadB);
}
}
test01输出
AAA加锁前
java.lang.Object 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) e5 01 00 f8 (11100101 00000001 00000000 11111000) (-134217243)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
AAA加锁中
java.lang.Object object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 05 98 f2 f9 (00000101 10011000 11110010 11111001) (-101541883)
4 4 (object header) d4 01 00 00 (11010100 00000001 00000000 00000000) (468)
8 4 (object header) e5 01 00 f8 (11100101 00000001 00000000 11111000) (-134217243)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
AAA加锁后
java.lang.Object object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 05 98 f2 f9 (00000101 10011000 11110010 11111001) (-101541883)
4 4 (object header) d4 01 00 00 (11010100 00000001 00000000 00000000) (468)
8 4 (object header) e5 01 00 f8 (11100101 00000001 00000000 11111000) (-134217243)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
BBB加锁前
java.lang.Object object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 05 98 f2 f9 (00000101 10011000 11110010 11111001) (-101541883)
4 4 (object header) d4 01 00 00 (11010100 00000001 00000000 00000000) (468)
8 4 (object header) e5 01 00 f8 (11100101 00000001 00000000 11111000) (-134217243)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
BBB加锁中
java.lang.Object object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 20 f6 3f 4f (00100000 11110110 00111111 01001111) (1329591840)
4 4 (object header) 54 00 00 00 (01010100 00000000 00000000 00000000) (84)
8 4 (object header) e5 01 00 f8 (11100101 00000001 00000000 11111000) (-134217243)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
BBB加锁后
java.lang.Object 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) e5 01 00 f8 (11100101 00000001 00000000 11111000) (-134217243)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
Process finished with exit code 0
test02输出
AAA加锁前java.lang.Object 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) e5 01 00 f8 (11100101 00000001 00000000 11111000) (-134217243)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
AAA加锁中java.lang.Object object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 05 f0 6d b0 (00000101 11110000 01101101 10110000) (-1334972411)
4 4 (object header) f0 01 00 00 (11110000 00000001 00000000 00000000) (496)
8 4 (object header) e5 01 00 f8 (11100101 00000001 00000000 11111000) (-134217243)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
BBB加锁前java.lang.Object object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 05 f0 6d b0 (00000101 11110000 01101101 10110000) (-1334972411)
4 4 (object header) f0 01 00 00 (11110000 00000001 00000000 00000000) (496)
8 4 (object header) e5 01 00 f8 (11100101 00000001 00000000 11111000) (-134217243)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
BBB加锁中java.lang.Object object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 6a c7 31 ad (01101010 11000111 00110001 10101101) (-1389246614)
4 4 (object header) f0 01 00 00 (11110000 00000001 00000000 00000000) (496)
8 4 (object header) e5 01 00 f8 (11100101 00000001 00000000 11111000) (-134217243)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
test03输出
AAA加锁前java.lang.Object 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) e5 01 00 f8 (11100101 00000001 00000000 11111000) (-134217243)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
AAA加锁中java.lang.Object object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 05 f0 87 c9 (00000101 11110000 10000111 11001001) (-913838075)
4 4 (object header) ed 02 00 00 (11101101 00000010 00000000 00000000) (749)
8 4 (object header) e5 01 00 f8 (11100101 00000001 00000000 11111000) (-134217243)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
BBB加锁前java.lang.Object object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 05 f0 87 c9 (00000101 11110000 10000111 11001001) (-913838075)
4 4 (object header) ed 02 00 00 (11101101 00000010 00000000 00000000) (749)
8 4 (object header) e5 01 00 f8 (11100101 00000001 00000000 11111000) (-134217243)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
BBB加锁中java.lang.Object object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) a0 ed 0f 62 (10100000 11101101 00001111 01100010) (1645211040)
4 4 (object header) ef 00 00 00 (11101111 00000000 00000000 00000000) (239)
8 4 (object header) e5 01 00 f8 (11100101 00000001 00000000 11111000) (-134217243)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
test04输出
AAA加锁中java.lang.Object object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE # 偏向锁
0 4 (object header) 05 b8 f1 71 (00000101 10111000 11110001 01110001) (1911666693)
4 4 (object header) 82 02 00 00 (10000010 00000010 00000000 00000000) (642)
8 4 (object header) e5 01 00 f8 (11100101 00000001 00000000 11111000) (-134217243)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
BBB加锁中java.lang.Object object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE # 重量级锁
0 4 (object header) ca 4e b8 6e (11001010 01001110 10111000 01101110) (1857572554)
4 4 (object header) 82 02 00 00 (10000010 00000010 00000000 00000000) (642)
8 4 (object header) e5 01 00 f8 (11100101 00000001 00000000 11111000) (-134217243)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
再次加锁java.lang.Object object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE # 轻量级锁
0 4 (object header) a8 f5 6f cf (10101000 11110101 01101111 11001111) (-814746200)
4 4 (object header) f3 00 00 00 (11110011 00000000 00000000 00000000) (243)
8 4 (object header) e5 01 00 f8 (11100101 00000001 00000000 11111000) (-134217243)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
当锁处于偏向锁,又被另一个线程企图抢占时,偏向锁就会升级为轻量级锁。企图抢占的线程会通过自旋的形式尝试获取锁,不会阻塞抢锁线程,以便提高性能。 自旋原理非常简单,如果持有锁的线程能在很短时间内释放锁资源,那么那些等待竞争锁的线程就不需要进行内核态和用户态之间的切换来进入阻塞挂起状态,它们只需要等一等(自旋),等持有锁的线程释放锁后即可立即获取锁,这样就避免了用户线程和内核切换的消耗。 但是,线程自旋是需要消耗CPU的,如果一直获取不到锁,那么线程也不能一直占用CPU自旋做无用功,所以需要设定一个自旋等待的最大时间。JVM对于自旋周期的选择,JDK 1.6之后引入了适应性自旋锁,适应性自旋锁意味着自旋的时间不是固定的,而是由前一次在同一个锁上的自旋时间以及锁的拥有者的状态来决定的。线程如果自旋成功了,下次自旋的次数就会更多,如果自旋失败了,自旋的次数就会减少。 如果持有锁的线程执行的时间超过自旋等待的最大时间仍没有释放锁,就会导致其他争用锁的线程在最大等待时间内还是获取不到锁,自旋不会一直持续下去,这时争用线程会停止自旋进入阻塞状态,该锁膨胀为重量级锁。
# 重偏向和批量锁撤销
A 线程获取偏向锁成功,已经退出执行不再是活跃线程; B线程过来获取偏向锁,
- 默认前20次直接升级为轻量级锁
- 默认20次以后,直接偏向线程 B
- 达到40次阈值后,若再有其他线程C过来争抢,则触发批量撤销。该Class对象不再有任何偏向锁的情况(加锁的话直接轻量级锁起步)。
偏向锁升级为轻量级锁依赖于Class对象,而不是new的object实例
import org.openjdk.jol.info.ClassLayout;
import java.util.ArrayList;
import java.util.List;
/**
* @author starry
* @version 1.0
* @date 2022/5/31 10:26
* @Description 轻量级锁重偏向,批量锁撤销;基于Class
*/
public class SyncLockFlag {
public static void main(String[] args) throws InterruptedException {
// 重偏向
//test01();
// 批量撤销
test02();
}
private static void test02() {
List<Object> list = new ArrayList<>();
Thread threadA = new Thread(() -> {
for (int i = 0; i < 40; i++) {
Object obj = new Object();
list.add(obj);
System.out.println(i + 1 + "AAA加锁前" + ClassLayout.parseInstance(obj).toPrintable());
synchronized (obj) {
System.out.println(i + 1 + "AAA加锁中" + ClassLayout.parseInstance(obj).toPrintable());
}
System.out.println(i + 1 + "AAA加锁后" + ClassLayout.parseInstance(obj).toPrintable());
}
});
Thread threadB = new Thread(() -> {
try {
threadA.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
for (int i = 0; i < 40; i++) {
Object obj = list.get(i);
System.out.println(i + 1 + "BBB加锁前" + ClassLayout.parseInstance(obj).toPrintable());
synchronized (obj) {
System.out.println(i + 1 + "BBB加锁中" + ClassLayout.parseInstance(obj).toPrintable());
}
System.out.println(i + 1 + "BBB加锁后" + ClassLayout.parseInstance(obj).toPrintable());
//try {
// Thread.sleep(1100);
//} catch (InterruptedException e) {
// e.printStackTrace();
//}
}
});
Thread threadC = new Thread(() -> {
try {
threadB.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
for (int i = 0; i < 40; i++) {
Object obj = list.get(i);
System.out.println(i + 1 + "CCC加锁前" + ClassLayout.parseInstance(obj).toPrintable());
synchronized (obj) {
System.out.println(i + 1 + "CCC加锁中" + ClassLayout.parseInstance(obj).toPrintable());
}
System.out.println(i + 1 + "CCC加锁后" + ClassLayout.parseInstance(obj).toPrintable());
//try {
// Thread.sleep(1100);
//} catch (InterruptedException e) {
// e.printStackTrace();
//}
}
});
threadA.start();
threadB.start();
threadC.start();
try {
threadC.join();
Object newObj = new Object();
System.out.println("新对象"+ClassLayout.parseInstance(newObj).toPrintable());
synchronized (newObj) {
System.out.println("新对象加锁"+ClassLayout.parseInstance(newObj).toPrintable());
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
private static void test01() throws InterruptedException {
List<Object> list = new ArrayList<>();
Thread threadA = new Thread(() -> {
for (int i = 0; i < 20; i++) {
Object obj = new Object();
list.add(obj);
System.out.println(i + 1 + "AAA加锁前" + ClassLayout.parseInstance(obj).toPrintable());
synchronized (obj) {
System.out.println(i + 1 + "AAA加锁中" + ClassLayout.parseInstance(obj).toPrintable());
}
System.out.println(i + 1 + "AAA加锁后" + ClassLayout.parseInstance(obj).toPrintable());
}
});
Thread threadB = new Thread(() -> {
try {
threadA.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
for (int i = 0; i < 20; i++) {
Object obj = list.get(i);
System.out.println(i + 1 + "BBB加锁前" + ClassLayout.parseInstance(obj).toPrintable());
synchronized (obj) {
System.out.println(i + 1 + "BBB加锁中" + ClassLayout.parseInstance(obj).toPrintable());
}
System.out.println(i + 1 + "BBB加锁后" + ClassLayout.parseInstance(obj).toPrintable());
}
});
threadA.start();
threadB.start();
try {
threadB.join();
Object newObj = new Object();
System.out.println("新对象"+ClassLayout.parseInstance(newObj).toPrintable());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
test01:threadA向list添加20个对象,并对这20个对象加锁;threadB等threadA运行结束,从list中取20个对象加锁
1AAA加锁前java.lang.Object 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) e5 01 00 f8 (11100101 00000001 00000000 11111000) (-134217243)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
1AAA加锁中java.lang.Object object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 05 a0 ea 6d (00000101 10100000 11101010 01101101) (1844092933)
4 4 (object header) 1f 02 00 00 (00011111 00000010 00000000 00000000) (543)
8 4 (object header) e5 01 00 f8 (11100101 00000001 00000000 11111000) (-134217243)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
1AAA加锁后java.lang.Object object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 05 a0 ea 6d (00000101 10100000 11101010 01101101) (1844092933)
4 4 (object header) 1f 02 00 00 (00011111 00000010 00000000 00000000) (543)
8 4 (object header) e5 01 00 f8 (11100101 00000001 00000000 11111000) (-134217243)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
# ...省略部分输出
20AAA加锁前java.lang.Object 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) e5 01 00 f8 (11100101 00000001 00000000 11111000) (-134217243)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
20AAA加锁中java.lang.Object object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 05 a0 ea 6d (00000101 10100000 11101010 01101101) (1844092933)
4 4 (object header) 1f 02 00 00 (00011111 00000010 00000000 00000000) (543)
8 4 (object header) e5 01 00 f8 (11100101 00000001 00000000 11111000) (-134217243)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
20AAA加锁后java.lang.Object object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 05 a0 ea 6d (00000101 10100000 11101010 01101101) (1844092933)
4 4 (object header) 1f 02 00 00 (00011111 00000010 00000000 00000000) (543)
8 4 (object header) e5 01 00 f8 (11100101 00000001 00000000 11111000) (-134217243)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
1BBB加锁前java.lang.Object object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 05 a0 ea 6d (00000101 10100000 11101010 01101101) (1844092933)
4 4 (object header) 1f 02 00 00 (00011111 00000010 00000000 00000000) (543)
8 4 (object header) e5 01 00 f8 (11100101 00000001 00000000 11111000) (-134217243)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
1BBB加锁中java.lang.Object object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 88 f2 2f fd (10001000 11110010 00101111 11111101) (-47189368)
4 4 (object header) c3 00 00 00 (11000011 00000000 00000000 00000000) (195)
8 4 (object header) e5 01 00 f8 (11100101 00000001 00000000 11111000) (-134217243)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
1BBB加锁后java.lang.Object 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) e5 01 00 f8 (11100101 00000001 00000000 11111000) (-134217243)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
# ...省略部分输出
16BBB加锁前java.lang.Object object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 05 a0 ea 6d (00000101 10100000 11101010 01101101) (1844092933)
4 4 (object header) 1f 02 00 00 (00011111 00000010 00000000 00000000) (543)
8 4 (object header) e5 01 00 f8 (11100101 00000001 00000000 11111000) (-134217243)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
16BBB加锁中java.lang.Object object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 88 f2 2f fd (10001000 11110010 00101111 11111101) (-47189368)
4 4 (object header) c3 00 00 00 (11000011 00000000 00000000 00000000) (195)
8 4 (object header) e5 01 00 f8 (11100101 00000001 00000000 11111000) (-134217243)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
16BBB加锁后java.lang.Object 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) e5 01 00 f8 (11100101 00000001 00000000 11111000) (-134217243)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
17BBB加锁前java.lang.Object object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 05 a0 ea 6d (00000101 10100000 11101010 01101101) (1844092933)
4 4 (object header) 1f 02 00 00 (00011111 00000010 00000000 00000000) (543)
8 4 (object header) e5 01 00 f8 (11100101 00000001 00000000 11111000) (-134217243)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
17BBB加锁中java.lang.Object object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 05 b1 ea 6d (00000101 10110001 11101010 01101101) (1844097285)
4 4 (object header) 1f 02 00 00 (00011111 00000010 00000000 00000000) (543)
8 4 (object header) e5 01 00 f8 (11100101 00000001 00000000 11111000) (-134217243)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
17BBB加锁后java.lang.Object object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 05 b1 ea 6d (00000101 10110001 11101010 01101101) (1844097285)
4 4 (object header) 1f 02 00 00 (00011111 00000010 00000000 00000000) (543)
8 4 (object header) e5 01 00 f8 (11100101 00000001 00000000 11111000) (-134217243)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
# ...省略部分输出
20BBB加锁前java.lang.Object object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 05 a0 ea 6d (00000101 10100000 11101010 01101101) (1844092933)
4 4 (object header) 1f 02 00 00 (00011111 00000010 00000000 00000000) (543)
8 4 (object header) e5 01 00 f8 (11100101 00000001 00000000 11111000) (-134217243)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
20BBB加锁中java.lang.Object object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 05 b1 ea 6d (00000101 10110001 11101010 01101101) (1844097285)
4 4 (object header) 1f 02 00 00 (00011111 00000010 00000000 00000000) (543)
8 4 (object header) e5 01 00 f8 (11100101 00000001 00000000 11111000) (-134217243)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
20BBB加锁后java.lang.Object object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 05 b1 ea 6d (00000101 10110001 11101010 01101101) (1844097285)
4 4 (object header) 1f 02 00 00 (00011111 00000010 00000000 00000000) (543)
8 4 (object header) e5 01 00 f8 (11100101 00000001 00000000 11111000) (-134217243)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
新对象java.lang.Object object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 05 01 00 00 (00000101 00000001 00000000 00000000) (261)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) e5 01 00 f8 (11100101 00000001 00000000 11111000) (-134217243)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
threadA都是偏向锁,新建对象时偏向锁的初始状态,加锁中及加锁后都是偏向threadA threadB加锁前都是偏向threadA的
- 前16次加锁都是轻量级锁,加锁后都是无锁状态
- 第17次及以后都是重新偏向threadB,加锁后都是偏向threadB
- 重偏向后,新建对象的状态为初始化的偏向状态
这里测试的是从第17次开始重偏向新线程,但是默认阈值是20(20次及以后,不同次版本,不同机器配置不一样)
test02:threadA向list添加40个对象,并对这40个对象加锁;threadB等threadA运行结束,从list中取40个对象加锁;threadC等threadB运行结束,从list中取40个对象加锁
1AAA加锁前java.lang.Object 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) e5 01 00 f8 (11100101 00000001 00000000 11111000) (-134217243)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
1AAA加锁中java.lang.Object object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 05 20 5b f7 (00000101 00100000 01011011 11110111) (-145022971)
4 4 (object header) 6d 02 00 00 (01101101 00000010 00000000 00000000) (621)
8 4 (object header) e5 01 00 f8 (11100101 00000001 00000000 11111000) (-134217243)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
1AAA加锁后java.lang.Object object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 05 20 5b f7 (00000101 00100000 01011011 11110111) (-145022971)
4 4 (object header) 6d 02 00 00 (01101101 00000010 00000000 00000000) (621)
8 4 (object header) e5 01 00 f8 (11100101 00000001 00000000 11111000) (-134217243)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
# ...省略部分输出
40AAA加锁前java.lang.Object 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) e5 01 00 f8 (11100101 00000001 00000000 11111000) (-134217243)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
40AAA加锁中java.lang.Object object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 05 20 5b f7 (00000101 00100000 01011011 11110111) (-145022971)
4 4 (object header) 6d 02 00 00 (01101101 00000010 00000000 00000000) (621)
8 4 (object header) e5 01 00 f8 (11100101 00000001 00000000 11111000) (-134217243)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
40AAA加锁后java.lang.Object object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 05 20 5b f7 (00000101 00100000 01011011 11110111) (-145022971)
4 4 (object header) 6d 02 00 00 (01101101 00000010 00000000 00000000) (621)
8 4 (object header) e5 01 00 f8 (11100101 00000001 00000000 11111000) (-134217243)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
1BBB加锁前java.lang.Object object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 05 20 5b f7 (00000101 00100000 01011011 11110111) (-145022971)
4 4 (object header) 6d 02 00 00 (01101101 00000010 00000000 00000000) (621)
8 4 (object header) e5 01 00 f8 (11100101 00000001 00000000 11111000) (-134217243)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
1BBB加锁中java.lang.Object object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) a8 f2 8f 03 (10101000 11110010 10001111 00000011) (59765416)
4 4 (object header) 92 00 00 00 (10010010 00000000 00000000 00000000) (146)
8 4 (object header) e5 01 00 f8 (11100101 00000001 00000000 11111000) (-134217243)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
1BBB加锁后java.lang.Object 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) e5 01 00 f8 (11100101 00000001 00000000 11111000) (-134217243)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
# 第16后轻量级锁(重偏向)
17BBB加锁前java.lang.Object object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 05 20 5b f7 (00000101 00100000 01011011 11110111) (-145022971)
4 4 (object header) 6d 02 00 00 (01101101 00000010 00000000 00000000) (621)
8 4 (object header) e5 01 00 f8 (11100101 00000001 00000000 11111000) (-134217243)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
17BBB加锁中java.lang.Object object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 05 31 5b f7 (00000101 00110001 01011011 11110111) (-145018619)
4 4 (object header) 6d 02 00 00 (01101101 00000010 00000000 00000000) (621)
8 4 (object header) e5 01 00 f8 (11100101 00000001 00000000 11111000) (-134217243)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
17BBB加锁后java.lang.Object object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 05 31 5b f7 (00000101 00110001 01011011 11110111) (-145018619)
4 4 (object header) 6d 02 00 00 (01101101 00000010 00000000 00000000) (621)
8 4 (object header) e5 01 00 f8 (11100101 00000001 00000000 11111000) (-134217243)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
40BBB加锁前java.lang.Object object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 05 20 5b f7 (00000101 00100000 01011011 11110111) (-145022971)
4 4 (object header) 6d 02 00 00 (01101101 00000010 00000000 00000000) (621)
8 4 (object header) e5 01 00 f8 (11100101 00000001 00000000 11111000) (-134217243)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
40BBB加锁中java.lang.Object object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 05 31 5b f7 (00000101 00110001 01011011 11110111) (-145018619)
4 4 (object header) 6d 02 00 00 (01101101 00000010 00000000 00000000) (621)
8 4 (object header) e5 01 00 f8 (11100101 00000001 00000000 11111000) (-134217243)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
40BBB加锁后java.lang.Object object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 05 31 5b f7 (00000101 00110001 01011011 11110111) (-145018619)
4 4 (object header) 6d 02 00 00 (01101101 00000010 00000000 00000000) (621)
8 4 (object header) e5 01 00 f8 (11100101 00000001 00000000 11111000) (-134217243)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
1CCC加锁前java.lang.Object 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) e5 01 00 f8 (11100101 00000001 00000000 11111000) (-134217243)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
1CCC加锁中java.lang.Object object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) f8 ec 9f 03 (11111000 11101100 10011111 00000011) (60812536)
4 4 (object header) 92 00 00 00 (10010010 00000000 00000000 00000000) (146)
8 4 (object header) e5 01 00 f8 (11100101 00000001 00000000 11111000) (-134217243)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
1CCC加锁后java.lang.Object 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) e5 01 00 f8 (11100101 00000001 00000000 11111000) (-134217243)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
17CCC加锁前java.lang.Object object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 05 31 5b f7 (00000101 00110001 01011011 11110111) (-145018619)
4 4 (object header) 6d 02 00 00 (01101101 00000010 00000000 00000000) (621)
8 4 (object header) e5 01 00 f8 (11100101 00000001 00000000 11111000) (-134217243)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
17CCC加锁中java.lang.Object object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) f8 ec 9f 03 (11111000 11101100 10011111 00000011) (60812536)
4 4 (object header) 92 00 00 00 (10010010 00000000 00000000 00000000) (146)
8 4 (object header) e5 01 00 f8 (11100101 00000001 00000000 11111000) (-134217243)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
17CCC加锁后java.lang.Object 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) e5 01 00 f8 (11100101 00000001 00000000 11111000) (-134217243)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
40CCC加锁前java.lang.Object object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 05 31 5b f7 (00000101 00110001 01011011 11110111) (-145018619)
4 4 (object header) 6d 02 00 00 (01101101 00000010 00000000 00000000) (621)
8 4 (object header) e5 01 00 f8 (11100101 00000001 00000000 11111000) (-134217243)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
40CCC加锁中java.lang.Object object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) f8 ec 9f 03 (11111000 11101100 10011111 00000011) (60812536)
4 4 (object header) 92 00 00 00 (10010010 00000000 00000000 00000000) (146)
8 4 (object header) e5 01 00 f8 (11100101 00000001 00000000 11111000) (-134217243)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
40CCC加锁后java.lang.Object 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) e5 01 00 f8 (11100101 00000001 00000000 11111000) (-134217243)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
新对象java.lang.Object 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) e5 01 00 f8 (11100101 00000001 00000000 11111000) (-134217243)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
新对象加锁java.lang.Object object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 58 f0 9f 8a (01011000 11110000 10011111 10001010) (-1969229736)
4 4 (object header) 7e 00 00 00 (01111110 00000000 00000000 00000000) (126)
8 4 (object header) e5 01 00 f8 (11100101 00000001 00000000 11111000) (-134217243)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
Process finished with exit code 0
threadA的锁和上面的一样,初始化的偏向锁和偏向threadA的偏向锁 threadB的17次及以后加锁都是重偏向到threadB threadC的加锁中都是轻量级锁,没有重偏向threadC,因为在 _BiasedLockingBulkRevokeThreshold(25000ms) _时间范围内,超过了 BiasedLockingBulkRevokeThreshold(40) 次的重偏向,且线程数多于2个。此时就发生了锁的批量撤销 发生了批量撤销就没有轻量级锁了,所以新建的对象是无锁状态 0|01,以后加锁直接是轻量级锁 00。
# 轻量级锁
- 轻量级锁加锁
线程在执行同步块之前,JVM会先在当前线程的栈桢中创建用于存储锁记录的空间,并将对象头中的Mark Word复制到锁记录中,官方称为Displaced Mark Word。然后线程尝试使用CAS将对象头中的Mark Word替换为指向锁记录的指针。如果成功,当前线程获得锁,如果失败,表示其他线程竞争锁,当前线程便尝试使用自旋来获取锁。
- 轻量级锁解锁
轻量级解锁时,会使用原子的CAS操作将Displaced Mark Word替换回到对象头,如果成功,则表示没有竞争发生。如果失败,表示当前锁存在竞争,锁就会膨胀成重量级锁。
轻量级锁升级为重量级锁:这个时候,只要我们的线程发生了竞争,并且CAS替换失败,就会发起锁膨胀,升级为重量级锁(针对的是一个对象实例,非Class)。
默认情况下,自旋的次数为10次,用户可以通过
-XX:PreBlockSpin
选项来进行更改。 JDK 1.6的轻量级锁使用的是普通自旋锁,且需要使用-XX:+UseSpinning选项手工开启。 JDK 1.7后,轻量级锁使用自适应自旋锁,JVM启动时自动开启,且自旋时间由JVM自动控制。
import org.openjdk.jol.info.ClassLayout;
/**
* @author starry
* @version 1.0
* @date 2022/6/5 22:41
* @Description 轻量级锁
*/
public class SyncLightLock {
public static void main(String[] args) throws InterruptedException {
Object obj = new Object();
System.out.println("AAA加锁前" + ClassLayout.parseInstance(obj).toPrintable());
Thread threadA = new Thread(() -> {
synchronized (obj) {
try {
System.out.println("AAA加锁中" + ClassLayout.parseInstance(obj).toPrintable());
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
threadA.start();
Thread.sleep(500);
System.out.println("BBB加锁前" + ClassLayout.parseInstance(obj).toPrintable());
Thread threadB = new Thread(() -> {
synchronized (obj) {
System.out.println("BBB加锁中" + ClassLayout.parseInstance(obj).toPrintable());
}
});
threadB.start();
threadA.join();
threadB.join();
synchronized (obj) {
System.out.println("再次加锁"+ClassLayout.parseInstance(obj).toPrintable());
}
Object newObj = new Object();
synchronized (newObj) {
System.out.println("新对象加锁"+ClassLayout.parseInstance(newObj).toPrintable());
}
}
}
不配置偏向锁立即激活,方便轻量级锁升级;threadA持有锁sleep,threadB在threadA睡眠期间争抢锁,升级为重量级锁,再次对同一个对象加锁;对新建出来的对象加锁。
AAA加锁前java.lang.Object 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) e5 01 00 f8 (11100101 00000001 00000000 11111000) (-134217243)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
AAA加锁中java.lang.Object object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 58 ef 5f 4e (01011000 11101111 01011111 01001110) (1314910040)
4 4 (object header) 96 00 00 00 (10010110 00000000 00000000 00000000) (150)
8 4 (object header) e5 01 00 f8 (11100101 00000001 00000000 11111000) (-134217243)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
BBB加锁前java.lang.Object object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 58 ef 5f 4e (01011000 11101111 01011111 01001110) (1314910040)
4 4 (object header) 96 00 00 00 (10010110 00000000 00000000 00000000) (150)
8 4 (object header) e5 01 00 f8 (11100101 00000001 00000000 11111000) (-134217243)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
BBB加锁中java.lang.Object object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 3a 12 30 b8 (00111010 00010010 00110000 10111000) (-1204809158)
4 4 (object header) 7c 02 00 00 (01111100 00000010 00000000 00000000) (636)
8 4 (object header) e5 01 00 f8 (11100101 00000001 00000000 11111000) (-134217243)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
再次加锁java.lang.Object object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 3a 12 30 b8 (00111010 00010010 00110000 10111000) (-1204809158)
4 4 (object header) 7c 02 00 00 (01111100 00000010 00000000 00000000) (636)
8 4 (object header) e5 01 00 f8 (11100101 00000001 00000000 11111000) (-134217243)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
新对象加锁java.lang.Object object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 05 e0 a5 8f (00000101 11100000 10100101 10001111) (-1884954619)
4 4 (object header) 7c 02 00 00 (01111100 00000010 00000000 00000000) (636)
8 4 (object header) e5 01 00 f8 (11100101 00000001 00000000 11111000) (-134217243)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
- threadB CAS争抢失败,升级为重量级锁10
- 对已经是重量级锁的对象再次加锁,还是重量级锁锁,重量级加锁一直是重量级锁,不能降级
- 新建的对象不是重量级锁,说明,轻量级锁升级为重量级锁依赖于对象实例,并非Class
# ObjectMonitor
//Monitor结构体
ObjectMonitor::ObjectMonitor() {
_header = NULL;
_count = 0;
_waiters = 0,
//线程的重入次数
_recursions = 0;
_object = NULL;
//标识拥有该Monitor的线程
_owner = NULL;
//等待线程组成的双向循环链表
_WaitSet = NULL;
_WaitSetLock = 0 ;
_Responsible = NULL ;
_succ = NULL ;
//多线程竞争锁进入时的单向链表
cxq = NULL ;
FreeNext = NULL ;
//_owner从该双向循环链表中唤醒线程节点
_EntryList = NULL ;
_SpinFreq = 0 ;
_SpinClock = 0 ;
OwnerIsThread = 0 ;
}
ObjectMonitor的WaitSet、Cxq、EntryList这三个队列存放抢夺重量级锁的线程,而ObjectMonitor的Owner所指向的线程即为获得锁的线程。
- Cxq:竞争队列(Contention Queue),所有请求锁的线程首先被放在这个竞争队列中。
- EntryList:Cxq中那些有资格成为候选资源的线程被移动到EntryList中。
- WaitSet:某个拥有ObjectMonitor的线程在调用Object.wait()方法之后将被阻塞,然后该线程将被放置在WaitSet链表中。
ObjectMonitor的内部抢锁过程 Cxq Cxq并不是一个真正的队列,只是一个虚拟队列,原因在于Cxq是由Node及其next指针逻辑构成的,并不存在一个队列的数据结构。每次新加入Node会在Cxq的队头进行,通过CAS改变第一个节点的指针为新增节点,同时设置新增节点的next指向后续节点;从Cxq取得元素时,会从队尾获取。
EntryList EntryList与Cxq在逻辑上都属于等待队列。Cxq会被线程并发访问,为了降低对Cxq队尾的争用,而建立EntryList。在Owner线程释放锁时,JVM会从Cxq中迁移线程到EntryList,并会指定EntryList中的某个线程(一般为Head)为OnDeck Thread(Ready Thread)。EntryList中的线程作为候选竞争线程而存在。
OnDeck Thread与Owner Thread JVM不直接把锁传递给Owner Thread,而是把锁竞争的权利交给OnDeck Thread,OnDeck需要重新竞争锁。这样虽然牺牲了一些公平性,但是能极大地提升系统的吞吐量,在JVM中,也把这种选择行为称为“竞争切换”。 OnDeck Thread获取到锁资源后会变为Owner Thread。无法获得锁的OnDeck Thread则会依然留在EntryList中,考虑到公平性,OnDeck Thread在EntryList中的位置不发生变化(依然在队头)。 在OnDeck Thread成为Owner的过程中,还有一个不公平的事情,就是后来的新抢锁线程可能直接通过CAS自旋成为Owner而抢到锁。
WaitSet 如果Owner线程被Object.wait()方法阻塞,就转移到WaitSet队列中,直到某个时刻通过Object.notify()或者Object.notifyAll()唤醒,该线程就会重新进入EntryList中。
# 总结
总结一下synchronized的执行过程,大致如下:
- 线程抢锁时,JVM首先检测内置锁对象Mark Word中的biased_lock(偏向锁标识)是否设置成1,lock(锁标志位)是否为01,如果都满足,确认内置锁对象为可偏向状态。
- 在内置锁对象确认为可偏向状态之后,JVM检查Mark Word中的线程ID是否为抢锁线程ID,如果是,就表示抢锁线程处于偏向锁状态,抢锁线程快速获得锁,开始执行临界区代码。
- 如果Mark Word中的线程ID并未指向抢锁线程,就通过CAS操作竞争锁。
- 如果竞争成功,就将Mark Word中的线程ID设置为抢锁线程,偏向标志位设置为1,锁标志位设置为01,然后执行临界区代码,此时内置锁对象处于偏向锁状态。
- 如果CAS操作竞争失败,就说明发生了竞争,撤销偏向锁,进而升级为轻量级锁。
- JVM使用CAS将锁对象的Mark Word替换为抢锁线程的锁记录指针,如果成功,抢锁线程就获得锁。如果替换失败,就表示其他线程竞争锁,JVM尝试使用CAS自旋替换抢锁线程的锁记录指针,如果自旋成功(抢锁成功),那么锁对象依然处于轻量级锁状态。
- 如果JVM的CAS替换锁记录指针自旋失败,轻量级锁就膨胀为重量级锁,后面等待锁的线程也要进入阻塞状态。
总体来说,偏向锁是在没有发生锁争用的情况下使用的;一旦有了第二个线程争用锁,偏向锁就会升级为轻量级锁;如果锁争用很激烈,轻量级锁的CAS自旋到达阈值后,轻量级锁就会升级为重量级锁。
# JMM
JMM(Java Memory Model,Java内存模型)并不像JVM内存结构一样是真实存在的运行实体,更多体现为一种规范和规则。 JMM最初由JSR-133文档描述,JMM定义了一组规则或规范,该规范定义了一个线程对共享变量写入时,如何确保对另一个线程是可见的。实际上,JMM提供了合理的禁用缓存以及禁止重排序的方法,所以其核心的价值在于解决可见性和有序性。 JMM的另一大价值在于能屏蔽各种硬件和操作系统的访问差异,保证Java程序在各种平台下对内存的访问最终都是一致的。 Java内存模型规定所有的变量都存储在主存中,JMM的主存类似于物理内存,但有区别,还能包含部分共享缓存。每个Java线程都有自己的工作内存(类似于CPU高速缓存,但也有区别)。
- 主存:主要存储的是Java实例对象,所有线程创建的实例对象都存放在主存中,无论该实例对象是成员变量还是方法中的本地变量(也称局部变量),当然也包括共享的类信息、常量、静态变量。由于是共享数据区域,因此多条线程对同一个变量进行访问可能会发现线程安全问题。
- 工作内存:主要存储当前方法的所有本地变量信息(工作内存中存储着主存中的变量副本),每个线程只能访问自己的工作内存,即线程中的本地变量对其他线程是不可见的,即使两个线程执行的是同一段代码,它们也会各自在自己的工作内存中创建属于当前线程的本地变量,当然也包括字节码行号指示器、相关Native方法的信息。注意,由于工作内存是每个线程的私有数据,线程间无法相互访问工作内存,因此存储在工作内存的数据不存在线程安全问题。
# 指令重排
在执行程序时,为了提高性能,编译器和处理器常常会对指令做重排序。重排序分3种类型。
- 编译器优化的重排序。编译器在不改变单线程程序语义的前提下,可以重新安排语句的执行顺序。
- 指令级并行的重排序。现代处理器采用了指令级并行技术(Instruction-LevelParallelism,ILP)来将多条指令重叠执行。如果不存在数据依赖性,处理器可以改变语句对应
机器指令的执行顺序。
- 内存系统的重排序。由于处理器使用缓存和读/写缓冲区,这使得加载和存储操作看上去可能是在乱序执行。
对于处理器重排序,JMM的处理器重排序规则会要求 Java编译器 在生成指令序列时,插入特定类型的内存屏障(Memory Barriers,Intel称之为Memory Fence)指令,通过内存屏障指令来禁止特定类型的处理器重排序。
# 内存屏障
StoreLoad Barriers是一个“全能型”的屏障,它同时具有其他3个屏障的效果。现代的多处理器大多支持该屏障(其他类型的屏障不一定被所有处理器支持)。执行该屏障开销会很昂贵,因为当前处理器通常要把写缓冲区中的数据全部刷新到内存中(Buffer Fully Flush)。
# volatile
# 可见性
CPU的术语定义 X86处理器下通过工具获取JIT编译器生成的汇编指令
instance = new Singleton(); // instance是volatile变量
转成汇编
0x01a3de1d: movb $0×0,0×1104800(%esi);
0x01a3de24: lock addl $0×0,(%esp);
有volatile变量修饰的共享变量进行写操作的时候会多出第二行汇编代码,通过查IA-32架构软件开发者手册可知,Lock前缀的指令在多核处理器下会引发了两件事情
- 将当前处理器缓存行的数据写回到系统内存。
- 这个写回内存的操作会使在其他CPU里缓存了该内存地址的数据无效。
实现原则
- Lock前缀指令会引起处理器缓存回写到内存 。 Lock前缀指令导致在执行指令期间,声言处理器的LOCK#信号。 在多处理器环境中,LOCK#信号确保在声言该信号期间,处理器可以独占任何共享内存。 但是,在最近的处理器里,LOCK#信号一般不锁总线,而是锁缓存,毕竟锁总线开销的比较大。 对于Intel486和Pentium处理器,在锁操作时,总是在总线上声言LOCK#信号。 但在P6和目前的处理器中,如果访问的内存区域已经缓存在处理器内部,则不会声言LOCK#信号。相反,它会锁定这块内存区域的缓存并回写到内存,并使用缓存一致性机制来确保修改的原子性,此操作被称为“缓存锁定”,缓存一致性机制会阻止同时修改由两个以上处理器缓存的内存区域数据。
- 一个处理器的缓存回写到内存会导致其他处理器的缓存无效 。IA-32处理器和Intel 64处理器使用MESI(修改、独占、共享、无效)控制协议去维护内部缓存和其他处理器缓存的一致性。在多核处理器系统中进行操作的时候,IA-32和Intel 64处理器能嗅探其他处理器访问系统内存和它们的内部缓存。处理器使用嗅探技术保证它的内部缓存、系统内存和其他处理器的缓存的数据在总线上保持一致。例如,在Pentium和P6 family处理器中,如果通过嗅探一个处理器来检测其他处理器打算写内存地址,而这个地址当前处于共享状态,那么正在嗅探的处理器将使它的缓存行无效,在下次访问相同内存地址时,强制执行缓存行填充。 :::info 为了提高处理速度,处理器不直接和内存进行通信,而是先将系统内存的数据读到内部缓存(L1,L2或其他)后再进行操作,但操作完不知道何时会写到内存。如果对声明了volatile的变量进行写操作,JVM就会向处理器发送一条Lock前缀的指令,将这个变量所在缓存行的数据写回到系统内存。但是,就算写回到内存,如果其他处理器缓存的值还是旧的,再执行计算操作就会有问题。所以,在多处理器下,为了保证各个处理器的缓存是一致的,就会实现缓存一致性协议,每个处理器通过嗅探在总线上传播的数据来检查自己缓存的值是不是过期了,当处理器发现自己缓存行对应的内存地址被修改,就会将当前处理器的缓存行设置成无效状态,当处理器对这个数据进行修改操作的时候,会重新从系统内存中把数据读到处理器缓存里。 :::
# 使用优化
/** 队列中的头部节点 */
private transient final PaddedAtomicReference<QNode> head;
/** 队列中的尾部节点 */
private transient final PaddedAtomicReference<QNode> tail;
static final class PaddedAtomicReference <T> extends AtomicReference T> {
// 使用很多4个字节的引用追加到64个字节
Object p0, p1, p2, p3, p4, p5, p6, p7, p8, p9, pa, pb, pc, pd, pe;
PaddedAtomicReference(T r) {
super(r);
}
}
public class AtomicReference <V> implements java.io.Serializable {
private volatile V value;
// 省略其他代码
}
一个对象的引用占4个字节,它追加了15个变量(共占60个字节),再加上父类的value变量,一共64个字节。 如果队列的头节点和尾节点都不足64字节的话,处理器会将它们都读到同一个高速缓存行中,在多处理器下每个处理器都会缓存同样的头、尾节点,当一个处理器试图修改头节点时,会将整个缓存行锁定,那么在缓存一致性机制的作用下,会导致其他处理器不能访问自己高速缓存中的尾节点,而队列的入队和出队操作则需要不停修改头节点和尾节点,所以在多处理器的情况下将会严重影响到队列的入队和出队效率 是不是在使用volatile变量时都应该追加到64字节呢?
- 缓存行非64字节宽的处理器
- 共享变量不会被频繁地写
不过这种追加字节的方式在Java 7下可能不生效,因为Java 7变得更加智慧,它会淘汰或重新排列无用字段,需要使用其他追加字节的方式
# 禁止指令重排序
重排序分为编译器重排序和处理器重排序。为了实现volatile内存语义,JMM会分别限制这两种类型的重排序类型。 JMM针对编译器制定的volatile重排序规则表。
- 当第二个操作是volatile写时,不管第一个操作是什么,都不能重排序。这个规则确保volatile写之前的操作不会被编译器重排序到volatile写之后。
- 当第一个操作是volatile读时,不管第二个操作是什么,都不能重排序。这个规则确保volatile读之后的操作不会被编译器重排序到volatile读之前。
- 当第一个操作是volatile写,第二个操作是volatile读时,不能重排序。
为了实现volatile的内存语义,编译器在生成字节码时,会在指令序列中插入内存屏障来禁止特定类型的处理器重排序。对于编译器来说,发现一个最优布置来最小化插入屏障的总数几乎不可能。为此,JMM采取保守策略。下面是基于保守策略的JMM内存屏障插入策略。
- 在每个volatile写操作的前面插入一个StoreStore屏障。
- 在每个volatile写操作的后面插入一个StoreLoad屏障。
- 在每个volatile读操作的后面插入一个LoadLoad屏障。
- 在每个volatile读操作的后面插入一个LoadStore屏障。
上述内存屏障插入策略非常保守,但它可以保证在任意处理器平台,任意的程序中都能得到正确的volatile内存语义。
在保守策略下,volatile写插入内存屏障后生成的指令序列示意图 StoreStore屏障可以保证在volatile写之前,其前面的所有普通写操作已经对任意处理器可见了。这是因为StoreStore屏障将保障上面所有的普通写在volatile写之前刷新到主内存。 这里比较有意思的是,volatile写后面的StoreLoad屏障。此屏障的作用是避免volatile写与后面可能有的volatile读/写操作重排序。因为编译器常常无法准确判断在一个volatile写的后面是否需要插入一个StoreLoad屏障(比如,一个volatile写之后方法立即return)。为了保证能正确实现volatile的内存语义,JMM在采取了保守策略:在每个volatile写的后面,或者在每个volatile读的前面插入一个StoreLoad屏障。从整体执行效率的角度考虑,JMM最终选择了在每个volatile写的后面插入一个StoreLoad屏障。因为volatile写-读内存语义的常见使用模式是:一个写线程写volatile变量,多个读线程读同一个volatile变量。当读线程的数量大大超过写线程时,选择在volatile写之后插入StoreLoad屏障将带来可观的执行效率的提升。从这里可以看到JMM在实现上的一个特点:首先确保正确性,然后再去追求执行效率。
在保守策略下,volatile读插入内存屏障后生成的指令序列示意图 LoadLoad屏障用来禁止处理器把上面的volatile读与下面的普通读重排序。 LoadStore屏障用来禁止处理器把上面的volatile读与下面的普通写重排序。
:::info 上述volatile写和volatile读的内存屏障插入策略非常保守。在实际执行时,只要不改变volatile写-读的内存语义,编译器可以根据具体情况省略不必要的屏障。 :::
# AQS
队列同步器AbstractQueuedSynchronizer(以下简称同步器),是用来构建锁或者其他同步组件的基础框架,它使用了一个int成员变量表示同步状态,通过内置的FIFO队列来完成资源获取线程的排队工作,并发包的作者(Doug Lea)期望它能够成为实现大部分同步需求的基础。 同步器的主要使用方式是继承,子类通过继承同步器并实现它的抽象方法来管理同步状态,在抽象方法的实现过程中免不了要对同步状态进行更改,这时就需要使用同步器提供的3个方法(getState()、setState(int newState)和compareAndSetState(int expect,int update))来进行操作,因为它们能够保证状态的改变是安全的。 子类推荐被定义为自定义同步组件的静态内部类,同步器自身没有实现任何同步接口,它仅仅是定义了若干同步状态获取和释放的方法来供自定义同步组件使用,同步器既可以支持独占式地获取同步状态,也可以支持共享式地获取同步状态,这样就可以方便实现不同类型的同步组件(ReentrantLock、ReentrantReadWriteLock和CountDownLatch等)。 同步器是实现锁(也可以是任意同步组件)的关键,在锁的实现中聚合同步器,利用同步器实现锁的语义。
同步器的设计是基于模板方法模式的,也就是说,使用者需要继承同步器并重写指定的方法,随后将同步器组合在自定义同步组件的实现中,并调用同步器提供的模板方法,而这些模板方法将会调用使用者重写的方法。 重写同步器指定的方法时,需要使用同步器提供的如下3个方法来访问或修改同步状态。
- getState():获取当前同步状态。
- setState(int newState):设置当前同步状态。
- compareAndSetState(int expect,int update):使用CAS设置当前状态,该方法能够保证状态设置的原子性。
同步器可重写的方法
# 同步队列
同步器依赖内部的同步队列(一个FIFO双向队列)来完成同步状态的管理,当前线程获取同步状态失败时,同步器会将当前线程以及等待状态等信息构造成为一个节点(Node)并将其加入同步队列,同时会阻塞当前线程,当同步状态释放时,会把首节点中的线程唤醒,使其再次尝试获取同步状态。 同步队列中的节点(Node)用来保存获取同步状态失败的线程引用、等待状态以及前驱和后继节点。 节点是构成同步队列的基础,同步器拥有首节点(head)和尾节点(tail),没有成功获取同步状态的线程将会成为节点加入该队列的尾部 同步器包含了两个节点类型的引用,一个指向头节点,而另一个指向尾节点。试想一下,当一个线程成功地获取了同步状态(或者锁),其他线程将无法获取到同步状态,转而被构造成为节点并加入到同步队列中,而这个加入队列的过程必须要保证线程安全,因此同步器提供了一个基于CAS的设置尾节点的方法:compareAndSetTail(Nodeexpect,Nodeupdate),它需要传递当前线程“认为”的尾节点和当前节点,只有设置成功后,当前节点才正式与之前的尾节点建立关联。 同步队列遵循FIFO,首节点是获取同步状态成功的节点,首节点的线程在释放同步状态时,将会唤醒后继节点,而后继节点将会在获取同步状态成功时将自己设置为首节点。设置首节点是通过获取同步状态成功的线程来完成的,由于只有一个线程能够成功获取到同步状态,因此设置头节点的方法并不需要使用CAS来保证,它只需要将首节点设置成为原首节点的后继节点并断开原首节点的next引用即可。
# acquire
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
- 调用自定义同步器实现的
tryAcquire(int arg)
方法,该方法保证线程安全的获取同步状态, - 如果同步状态获取失败,则构造同步节点(独占
Node.EXCLUSIVE
,同一时刻只能有一个线程成功获取同步状态)并通过addWaiter(Node node)
方法将该节点加入到同步队列的尾部, - 最后调用
acquireQueued(Node node,int arg)
方法,使得该节点以“死循环”的方式获取同步状态。
如果获取不到则阻塞节点中的线程,而被阻塞线程的唤醒主要依靠前驱节点的出队或阻塞线程被中断来实现。
private Node addWaiter(Node mode) {
Node node = new Node(Thread.currentThread(), mode);
// 快速尝试在尾部添加
Node pred = tail;
if (pred != null) {
// 尝试(一次)cas替换尾节点
node.prev = pred;
if (compareAndSetTail(pred, node)) {
pred.next = node;
// 一次cas就成功,直接返回
return node;
}
}
// 尝试失败,死循环设置尾节点
enq(node);
return node;
}
private Node enq(final Node node) {
for (;;) {
Node t = tail;
if (t == null) { // Must initialize
// 初始化头尾节点
if (compareAndSetHead(new Node()))
tail = head;
} else {
// cas替换尾节点
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
final Node p = node.predecessor();
// 当前节点的前一个节点是头节点 并且 成功获取到同步状态
if (p == head && tryAcquire(arg)) {
// 把当前节点设置为头节点,直接返回
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
int ws = pred.waitStatus;
if (ws == Node.SIGNAL)
/*
* This node has already set status asking a release
* to signal it, so it can safely park.
*/
// 前一个节点是等待状态,自己也要跟着等待
return true;
if (ws > 0) {
/*
* Predecessor was cancelled. Skip over predecessors and
* indicate retry.
*/
do {
// 前面节点被取消,跳过前一个节点,直到前一个节点不是CANCELLED状态
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
// 当前节点排队到前一个节点不是SIGNAL和CANCELLED的后面
pred.next = node;
} else {
/*
* waitStatus must be 0 or PROPAGATE. Indicate that we
* need a signal, but don't park yet. Caller will need to
* retry to make sure it cannot acquire before parking.
*/
// 把前一个节点状态设置为等待状态(前一个节点是等待状态,自己也要等待)
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
private final boolean parkAndCheckInterrupt() {
// 挂起线程
LockSupport.park(this);
// 清除interrupted状态
return Thread.interrupted();
}
private void cancelAcquire(Node node) {
// Ignore if node doesn't exist
if (node == null)
return;
node.thread = null;
// Skip cancelled predecessors
// 跳过cancelled的节点
Node pred = node.prev;
while (pred.waitStatus > 0)
node.prev = pred = pred.prev;
// predNext is the apparent node to unsplice. CASes below will
// fail if not, in which case, we lost race vs another cancel
// or signal, so no further action is necessary.
Node predNext = pred.next;
// Can use unconditional write instead of CAS here.
// After this atomic step, other Nodes can skip past us.
// Before, we are free of interference from other threads.
// 把当前节点设置为取消状态
node.waitStatus = Node.CANCELLED;
// If we are the tail, remove ourselves.
// 当前节点是最后一个节点,把当前节点的前一个节点设置为尾节点
if (node == tail && compareAndSetTail(node, pred)) {
compareAndSetNext(pred, predNext, null);
} else {
// If successor needs signal, try to set pred's next-link
// so it will get one. Otherwise wake it up to propagate.
int ws;
// 当前节点是中间节点
if (pred != head &&
((ws = pred.waitStatus) == Node.SIGNAL ||
(ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) &&
pred.thread != null) {
Node next = node.next;
if (next != null && next.waitStatus <= 0)
compareAndSetNext(pred, predNext, next);
} else {
unparkSuccessor(node);
}
node.next = node; // help GC
}
}
# release
public final boolean release(int arg) {
// 调用重写的tryRelease方法
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
// 唤醒头节点的后继节点
unparkSuccessor(h);
return true;
}
return false;
}
# 总结
在获取同步状态时,同步器维护一个同步队列,获取状态失败的线程都会被加入到队列中并在队列中进行自旋; 移出队列(或停止自旋)的条件是前驱节点为头节点且成功获取了同步状态。 在释放同步状态时,同步器调用tryRelease(int arg)方法释放同步状态,然后唤醒头节点的后继节点。