一、Monitor 原理
1. java 对象头
- 一个java对象在内存中由两部分组成:对象头+对象中的成员变量
- 例如:引用类型 Integer 占用的内存 (8字节的对象头 + 4字节的 int 类型值)
- 基本类型 int 占用的内存(4个字节)
32位虚拟机中的对象头
- 普通对象的对象头是8个字节,其中4个字节是Mark Word,4个字节是Kclass Word(是一个指针,指向该对象的类型,例如:User.class)
状态 State 为 Normal 时(无锁,最后3位是 0 01):
- 32 bits 的 Mark Word 中,有25 bits 是 hashcode,4 bits 是 age(分代年龄),1 bit 是 biased_lock(是否启用偏向锁,0:否,1:是),最后 2 bits 是加锁状态
状态 State 为 Biased 时(偏向锁,最后3位是 1 01):
- 32 bits 的 Mark Word 中,有23 bits 是 thread,2 bits 是 epoch,4 bits 是 age(分代年龄),1 bit 是 biased_lock(是否启用偏向锁,0:否,1:是),最后 2 bits 是加锁状态
状态 State 为 Lightweight Locked 时(轻量级锁,最后2位是 00) 状态 State 为 Heavyweight Locked 时(重量级锁,最后2位是 10) 状态 State 为 Marked for GC 时(该对象被垃圾回收,最后2位是 11)
64位虚拟机中的 Mark Word
2. Monitor 概念
- Monitor,直译为“监视器”,而操作系统领域一般翻译为“管程”,在java领域就是“对象锁”。
- 管程是指管理共享变量以及对共享变量操作的过程,让它们支持并发。翻译成Java领域的语言,就是管理类的状态变量,让这个类是线程安全的。
- synchronized关键字和wait()、notify()、notifyAll()这三个方法是Java中实现管程技术的组成部分。
- Monitor有两大作用:同步和互斥
- wait/notify基于monitor做的,monitor中有owner、entryList、waitSet
- synchronized关联了monitor,是在JVM层面实现的,源码是c++
- ReentrantLock是在java层面实现的类似monitor的作用
3. java对象 与 Monitor 之间的关系
- 每个 Java 对象都可以关联一个 Monitor 对象,如果使用 synchronized 给对象加锁(重量级锁)之后,该 对象头的Mark Word 中就被设置指向Monitor对象 的指针。
Monitor 结构如下
synchronized(obj) {
// 临界区代码
}
- 刚开始 Monitor 中 Owner 为 null
- 当 Thread-2 执行 synchronized(obj) 就会将 Monitor 的所有者 Owner 置为 Thread-2,Monitor中只能有一个 Owner
- 在 Thread-2 上锁的过程中,如果 Thread-3,Thread-4,Thread-5 也来执行 synchronized(obj),就会进入EntryList,此时线程状态变为BLOCKED状态
- Thread-2 执行完同步代码块的内容,然后唤醒 EntryList 中等待的线程来竞争锁,竞争是非公平的(synchronized是非公平锁)
- WaitSet 中的 Thread-0,Thread-1 是之前获得过锁的线程,此时的状态是 WAITING 状态,后面讲wait-notify 时会分析
注意:
- synchronized 必须是进入同一个对象的 monitor 才有上述的效果
- 不加 synchronized 的对象不会关联 monitor ,不遵从以上规则
二、synchronized 原理进阶
1. 从字节码层面理解 synchronized
static final Object lock = new Object();
static int counter = 0;
public static void main(String[] args) {
synchronized (lock) {
counter++;
}
}
对应的字节码为
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=3, args_size=1
0: getstatic #2 // <- 获取lock引用地址
3: dup // 复制一份存到临时变量 slot 1,为了后面解锁时用
4: astore_1 // lock引用 -> slot 1
5: monitorenter // 执行到synchronized, 将 lock对象 MarkWord 置为 Monitor 指针(hashcode:25 | age:4 | biased_lock:0 -> ptr_to_heavyweight_monitor:30)
6: getstatic #3 // <- 获取i
9: iconst_1 // 准备常数 1
10: iadd // +1
11: putstatic #3 // -> i
14: aload_1 // <- 从临时变量 slot 1 得到 lock引用,找到 Monitor
15: monitorexit // 将 lock对象 MarkWord 重置(ptr_to_heavyweight_monitor:30 -> hashcode:25 | age:4 | biased_lock:0), 唤醒 EntryList
16: goto 24
19: astore_2 // 异常对象e 存到 slot 2
20: aload_1 // <- 获取lock引用
21: monitorexit // 将 lock对象 MarkWord 重置, 唤醒 EntryList
22: aload_2 // <- 从 slot 2 获取 异常对象e
23: athrow // throw e 抛出异常
24: return
Exception table:
from to target type
6 16 19 any // 监测可能出现异常的范围 6 ~ 16(同步代码块中的内容),出现异常后跳到 19
19 22 19 any // 监测可能出现异常的范围 19 ~ 22
LineNumberTable:
line 8: 0
line 9: 6
line 10: 14
line 11: 24
LocalVariableTable:
Start Length Slot Name Signature
0 25 0 args [Ljava/lang/String;
StackMapTable: number_of_entries = 2
frame_type = 255 /* full_frame */
offset_delta = 19
locals = [ class "[Ljava/lang/String;", class java/lang/Object ]
stack = [ class java/lang/Throwable ]
frame_type = 250 /* chop */
offset_delta = 4
注意
- 方法级别的 synchronized 不会在字节码指令中有所体现
2. 小故事理解 synchronized
- 之前分析过 synchronized 工作方式是让每个对象关联 Monitor,Monitor 是真正的锁,但是 Monitor 是操作系统提供的,使用 Monitor 会对程序性能有影响,所以 JDK 6 之后对 synchronized 获取锁的方式进行了优化。
故事角色
- 老王 - JVM
- 小南 - 线程
- 小女 - 线程
- 房间 - 对象
- 房间门上 - 防盗锁 - Monitor
- 房间门上 - 小南书包 - 轻量级锁
- 房间门上 - 刻上小南大名 - 偏向锁
- 批量重刻名 - 一个类的偏向锁撤销到达 20 阈值
- 不能刻名字 - 批量撤销该类对象的偏向锁,设置该类不可偏向
小南要使用房间保证计算不被其它人干扰(原子性),最初,他用的是防盗锁(重量级锁),当上下文切换时,锁住门。这样,即使他离开了,别人也进不了门,他的工作就是安全的。
但是,很多情况下没人跟他来竞争房间的使用权。小女是要用房间,但使用的时间上是错开的,小南白天用,小女晚上用。每次上锁太麻烦了,有没有更简单的办法呢?
小南和小女商量了一下,约定不锁门了,而是谁用房间,谁把自己的书包挂在门口,但他们的书包样式都一样,因此每次进门前得翻翻书包,看课本是谁的(轻量级锁),如果是自己的,那么就可以进门,这样省的上锁解锁了。万一书包不是自己的,那么就在门外等,并通知对方下次用锁门的方式(重量级锁)。
后来,小女回老家了,很长一段时间都不会用这个房间。小南每次还是挂书包,翻书包,虽然比锁门省事了,但仍然觉得麻烦。
于是,小南干脆在门上刻上了自己的名字:【小南专属房间,其它人勿用】(偏向锁),下次来用房间时,只要名字还在,那么说明没人打扰,还是可以安全地使用房间。如果这期间有其它人要用这个房间,那么由使用者将小南刻的名字擦掉,升级为挂书包的方式(偏向锁 升级为 轻量级锁)。
同学们都放假回老家了,小南就膨胀了,在 20 个房间刻上了自己的名字,想进哪个进哪个。后来他自己放假回老家了,这时小女回来了(她也要用这些房间),结果就是得一个个地擦掉小南刻的名字,升级为挂书包的方式。老王觉得这成本有点高,提出了一种批量重刻名(批量偏向锁) 的方法,他让小女不用挂书包了,可以直接在门上刻上自己的名字
后来,刻名的现象越来越频繁,老王受不了了:算了,这些房间都不能刻名(设置对象为不可偏向) 了,只能挂书包
3. synchronized 之 轻量级锁
- 轻量级锁的使用场景:如果一个对象虽然有多线程要加锁,但加锁的时间是错开的(即没有竞争),那么可以使用轻量级锁来优化(将线程栈帧中的锁记录当做轻量级锁)。
- 轻量级锁对使用者是透明的,即语法仍然是 synchronized
假设有两个同步代码块,利用同一个对象加锁
static final Object obj = new Object();
public static void method1() {
synchronized( obj ) {
// 同步块 A,方法1 调用 方法2
method2();
}
}
public static void method2() {
synchronized( obj ) {
// 同步块 B
}
}
- 创建 锁记录(Lock Record)对象,每个线程的栈帧都会包含一个锁记录的结构,内部可以存储锁定对象的 Mark Word
- 让 锁记录中 Object reference 指向 锁对象,并尝试用 cas 替换 Object 的 Mark Word,将 Mark Word 的值 存入 锁记录
- 如果 cas 替换成功(01 被替换为 00),对象头 中存储了 锁记录地址和状态 00(轻量级锁) ,表示由该线程给对象加锁,这时图示如下
- 如果 cas 失败,有两种情况:
- 如果是其它线程已经持有了该 Object 的轻量级锁,这时表明有竞争,进入锁膨胀过程
- 如果是自己执行了 synchronized 锁重入,那么再添加一条 Lock Record 作为重入的计数(这种 cas 失败,不影响同一对象的锁重入)
- 当退出 synchronized 代码块(解锁时),如果有取值为 null 的锁记录,表示有锁重入,这时重置锁记录,让重入计数减一
- 当退出 synchronized 代码块(解锁时),锁记录的值不为 null,这时使用 cas 将 Mark Word 的值恢复给对象头
- 成功,则解锁成功(01 被替换为 00)
- 失败,说明轻量级锁进行了锁膨胀或已经升级为重量级锁,进入重量级锁解锁流程
4. synchronized 之 锁膨胀
- 如果在尝试加轻量级锁的过程中,CAS 操作无法成功,这时一种情况就是有其它线程为此对象加了轻量级锁(有竞争),这时需要进行锁膨胀,将轻量级锁 变为 重量级锁。
static Object obj = new Object();
public static void method1() {
synchronized( obj ) {
// 同步块
}
- 当 Thread-1 进行轻量级加锁时,Thread-0 已经对该对象加了轻量级锁
- Thread-1 加轻量级锁失败,进入锁膨胀流程
- 即为 Object 对象申请 Monitor 锁,让 Object 指向重量级锁地址,此时 Mark Word 最后 2bits 是 10
- 然后自己进入 Monitor 的 EntryList,状态变为 BLOCKED
- 当 Thread-0 退出同步块解锁时,使用 cas 将 Mark Word 的值恢复给对象头,失败。这时会进入重量级解锁流程,即按照 Monitor 地址找到 Monitor 对象,设置 Owner 为 null,唤醒 EntryList 中 BLOCKED 线程
4. synchronized 之 重量级锁自旋优化
- 重量级锁竞争的时候,还可以使用自旋来进行优化,如果当前线程自旋成功(即这时候持锁线程已经退出了同步块,释放了锁),这时当前线程就可以避免阻塞。
自旋重试成功的情况
自旋重试失败的情况
- 自旋会占用 CPU 时间,单核 CPU 自旋就是浪费,多核 CPU 自旋才能发挥优势。
- 在 Java 6 之后自旋锁是自适应的,比如对象刚刚的一次自旋操作成功过,那么认为这次自旋成功的可能性高,就多自旋几次;反之,就少自旋甚至不自旋,总之,比较智能,jvm会调整自旋次数。
- Java 7 之后不能控制是否开启自旋功能
5. synchronized 之 偏向锁
(1)偏向锁原理
- 轻量级锁在没有竞争时(只有自己这个线程),每次重入仍然需要执行 CAS 操作。
- Java 6 中引入了偏向锁来做进一步优化:只有第一次使用 CAS 将线程 ID 设置到对象的 Mark Word 头,之后发现这个线程 ID 是自己的就表示没有竞争,不用重新 CAS。以后只要不发生竞争,这个对象就归该线程所有
static final Object obj = new Object();
public static void m1() {
synchronized( obj ) {
// 同步块 A
m2();
}
}
public static void m2() {
synchronized( obj ) {
// 同步块 B
m3();
}
}
public static void m3() {
synchronized( obj ) {
// 同步块 C
}
}
轻量级锁每次都要进行CAS操作,尝试替换 Mark Word ,也会影响性能
偏向锁,只有第一次会进行CAS将 线程ID 替换 为 锁对象的Mark Word,以后每次只需比较线程ID是否为自身
(2)偏向锁状态
64位虚拟机的 Mark Word
- 一个对象创建时:
- 如果开启了偏向锁(默认开启),那么对象创建后,markword 值为 0x05 即最后 3 位为 101,这时它的thread、epoch、age 都为 0
- 偏向锁是默认是延迟的,不会在程序启动时立即生效,如果想避免延迟,可以加 VM 参数 -XX:BiasedLockingStartupDelay=0 来禁用延迟
- 如果没有开启偏向锁,那么对象创建后,markword 值为 0x01 即最后 3 位为 001,这时它的 hashcode、age 都为 0,第一次用到 hashcode 时才会赋值
@Slf4j(topic = "c.TestBiased")
public class TestBiased {
public static void main(String[] args) throws IOException, InterruptedException {
Dog d = new Dog();
// 默认是延迟开启偏向锁,所以一开始 markword 值最后3位是 001
log.debug(ClassLayout.parseInstance(d).toPrintable());
Thread.sleep(4000);
// 延迟4秒后,偏向锁开启成功,markword 值最后3位是 101
log.debug(ClassLayout.parseInstance(new Dog()).toPrintable());
}
}
class Dog {
}
15:23:58.932 c.TestBiased [main] - cn.itcast.n4.Dog 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) a4 9b 01 f8 (10100100 10011011 00000001 11111000) (-134112348)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
15:24:02.939 c.TestBiased [main] - cn.itcast.n4.Dog 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) a4 9b 01 f8 (10100100 10011011 00000001 11111000) (-134112348)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
- 补充:从打印结果可以看出,对象头 object header 占了12个字节,为了凑齐8的倍数,额外加了4个字节:Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
1. 配置VM options: -XX:BiasedLockingStartupDelay=0
@Slf4j(topic = "c.TestBiased")
public class TestBiased {
public static void main(String[] args) throws IOException, InterruptedException {
Dog d = new Dog();
log.debug(ClassLayout.parseInstance(d).toPrintable());
}
}
class Dog {
}
15:34:27.597 c.TestBiased [main] - cn.itcast.n4.Dog 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) a4 9b 01 f8 (10100100 10011011 00000001 11111000) (-134112348)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
2. synchronized 加偏向锁
@Slf4j(topic = "c.TestBiased")
public class TestBiased {
public static void main(String[] args) throws IOException, InterruptedException {
Dog d = new Dog();
log.debug("加锁前对象头:{}", ClassLayout.parseInstance(d).toPrintable());
// 由于开启了偏向锁,执行到 synchronized,会先加偏向锁
synchronized (d) {
log.debug("加锁后对象头:{}", ClassLayout.parseInstance(d).toPrintable());
}
log.debug("解锁后对象头:{}", ClassLayout.parseInstance(d).toPrintable());
}
}
class Dog {
}
15:48:46.033 c.TestBiased [main] - 加锁前对象头:cn.itcast.n4.Dog 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) a4 9b 01 f8 (10100100 10011011 00000001 11111000) (-134112348)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
15:48:46.040 c.TestBiased [main] - 加锁后对象头:cn.itcast.n4.Dog object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 05 40 3d 03 (00000101 01000000 00111101 00000011) (54345733)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) a4 9b 01 f8 (10100100 10011011 00000001 11111000) (-134112348)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
15:48:46.041 c.TestBiased [main] - 解锁后对象头:cn.itcast.n4.Dog object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 05 40 3d 03 (00000101 01000000 00111101 00000011) (54345733)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) a4 9b 01 f8 (10100100 10011011 00000001 11111000) (-134112348)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
- 加完偏向锁后,Mark Word 包含的信息 线程id 、锁状态(最后3位是101)等
- 处于偏向锁的对象解锁后,线程 id 仍存储于对象头中
3. 禁用偏向锁
- 有多个线程竞争时最好禁用偏向锁
- 在上面测试代码运行时添加 VM 参数 -XX:-UseBiasedLocking(冒号后面不要加空格) 禁用偏向锁,-XX:+UseBiasedLocking 是启用偏向锁
21:38:55.999 c.TestBiased [main] - 加锁前对象头:cn.itcast.n4.Dog 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) a4 8b 01 20 (10100100 10001011 00000001 00100000) (536972196)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
21:38:56.004 c.TestBiased [main] - 加锁后对象头:cn.itcast.n4.Dog object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) d8 f2 fe 02 (11011000 11110010 11111110 00000010) (50262744)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) a4 8b 01 20 (10100100 10001011 00000001 00100000) (536972196)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
21:38:56.005 c.TestBiased [main] - 解锁后对象头:cn.itcast.n4.Dog 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) a4 8b 01 20 (10100100 10001011 00000001 00100000) (536972196)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
- 禁用偏向锁后,再执行到 synchronized 代码块时,就会加 轻量级锁(最后2位是 00)
(3)撤销偏向锁
场景1. 调用对象的hashcode方法,会撤销偏向锁
- 配置VM 参数 -XX:BiasedLockingStartupDelay=0,测试 hashcode
@Slf4j(topic = "c.TestBiased")
public class TestBiased {
public static void main(String[] args) throws IOException, InterruptedException {
Dog d = new Dog();
// 调用hashcode后,则会禁用这个对象的偏向锁
d.hashCode();
log.debug("加锁前对象头:{}", ClassLayout.parseInstance(d).toPrintable());
// 由于开启了偏向锁,执行到 synchronized,会先加偏向锁
synchronized (d) {
log.debug("加锁后对象头:{}", ClassLayout.parseInstance(d).toPrintable());
}
log.debug("解锁后对象头:{}", ClassLayout.parseInstance(d).toPrintable());
}
}
class Dog {
}
21:50:42.650 c.TestBiased [main] - 加锁前对象头:cn.itcast.n4.Dog object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 01 bd de 68 (00000001 10111101 11011110 01101000) (1759427841)
4 4 (object header) 07 00 00 00 (00000111 00000000 00000000 00000000) (7)
8 4 (object header) a4 8b 01 20 (10100100 10001011 00000001 00100000) (536972196)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
21:50:42.674 c.TestBiased [main] - 加锁后对象头:cn.itcast.n4.Dog object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 98 f4 d1 02 (10011000 11110100 11010001 00000010) (47314072)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) a4 8b 01 20 (10100100 10001011 00000001 00100000) (536972196)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
21:50:42.685 c.TestBiased [main] - 解锁后对象头:cn.itcast.n4.Dog object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 01 bd de 68 (00000001 10111101 11011110 01101000) (1759427841)
4 4 (object header) 07 00 00 00 (00000111 00000000 00000000 00000000) (7)
8 4 (object header) a4 8b 01 20 (10100100 10001011 00000001 00100000) (536972196)
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方法后,会撤销偏向状态,将31位的hashcode填充到 Mark Word中,加的锁是轻量级锁
- 因为如果加了偏向锁后,有54位是线程id,没空间存hashcode了
- 补充:
- 加轻量级锁时,hashcode会存放在线程栈帧的锁记录中
- 加重量级锁时,hashcode会存放在 monitor 中
场景2. 其他线程使用偏向锁对象,会撤销偏向锁
@Slf4j(topic = "c.TestBiased")
public class TestBiased {
public static void main(String[] args) throws IOException, InterruptedException {
Dog d = new Dog();
Thread t1 = new Thread(() -> {
synchronized (d) {
log.debug("t1线程加锁后对象头:{}", ClassLayout.parseInstance(d).toPrintable());
}
// 为了不让 t1、t2线程出现竞争,使用不同锁对象进行同步等待
// t1 执行完后,唤醒 t2
synchronized (TestBiased.class) {
TestBiased.class.notify();
}
}, "t1");
t1.start();
Thread t2 = new Thread(() -> {
synchronized (TestBiased.class) {
try {
TestBiased.class.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
log.debug("t2线程加锁前对象头:{}",ClassLayout.parseInstance(d).toPrintable());
synchronized (d) {
log.debug("t2线程加锁后对象头:{}",ClassLayout.parseInstance(d).toPrintable());
}
log.debug("t2线程解锁后对象头:{}",ClassLayout.parseInstance(d).toPrintable());
}, "t2");
t2.start();
}
class Dog {
}
22:22:08.566 c.TestBiased [t1] - t1线程加锁后对象头:cn.itcast.n4.Dog object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 05 a8 d8 19 (00000101 10101000 11011000 00011001) (433629189)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) a4 8b 01 20 (10100100 10001011 00000001 00100000) (536972196)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
22:22:08.571 c.TestBiased [t2] - t2线程加锁前对象头:cn.itcast.n4.Dog object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 05 a8 d8 19 (00000101 10101000 11011000 00011001) (433629189)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) a4 8b 01 20 (10100100 10001011 00000001 00100000) (536972196)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
22:22:08.574 c.TestBiased [t2] - t2线程加锁后对象头:cn.itcast.n4.Dog object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 70 f2 74 1a (01110000 11110010 01110100 00011010) (443871856)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) a4 8b 01 20 (10100100 10001011 00000001 00100000) (536972196)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
22:22:08.575 c.TestBiased [t2] - t2线程解锁后对象头:cn.itcast.n4.Dog 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) a4 8b 01 20 (10100100 10001011 00000001 00100000) (536972196)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
场景3. 多个线程调用 wait/notify,会撤销偏向锁
@Slf4j(topic = "c.TestBiased")
public class TestBiased {
public static void main(String[] args) throws IOException, InterruptedException {
Dog d = new Dog();
Thread t1 = new Thread(() -> {
log.debug("t1线程加锁前对象头:{}", ClassLayout.parseInstance(d).toPrintable());
synchronized (d) {
log.debug("t1线程加锁后对象头:{}", ClassLayout.parseInstance(d).toPrintable());
try {
d.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
log.debug("t1线程被t2唤醒后对象头:{}", ClassLayout.parseInstance(d).toPrintable());
}
}, "t1");
t1.start();
new Thread(() -> {
try {
Thread.sleep(6000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (d) {
log.debug("t2线程唤醒t1线程\n");
d.notify();
}
}, "t2").start();
}
class Dog {
}
22:31:00.068 c.TestBiased [t1] - t1线程加锁前对象头:cn.itcast.n4.Dog 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) a4 8b 01 20 (10100100 10001011 00000001 00100000) (536972196)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
22:31:00.091 c.TestBiased [t1] - t1线程加锁后对象头:cn.itcast.n4.Dog object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 05 c8 50 1a (00000101 11001000 01010000 00011010) (441501701)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) a4 8b 01 20 (10100100 10001011 00000001 00100000) (536972196)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
22:31:02.006 c.TestBiased [t2] - t2线程唤醒t1线程
22:31:02.007 c.TestBiased [t1] - t1线程被t2唤醒后对象头:cn.itcast.n4.Dog object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) ea d1 2f 18 (11101010 11010001 00101111 00011000) (405787114)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) a4 8b 01 20 (10100100 10001011 00000001 00100000) (536972196)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
- t1线程加锁后是偏向锁,然后 t1 等待 t2,t2 唤醒 t1后,撤销偏向锁,直接升级为重量级锁
(4)批量重偏向
- 虽然对象被多个线程访问,但没有竞争,这时偏向了线程 t1 的对象仍有机会重新偏向 t2,重偏向会重置对象的 Thread ID(即将t1 Thread ID 改为 t2 Thread ID) 例如:t1、t2 没有竞争,执行到t1的同步代码块时,t1会加偏向锁,再执行到t2的同步代码块时,t2会撤销偏向锁,如果这样反复撤销次数达到阈值20时,就会给t2加偏向锁
- 因为撤销偏向锁升级为轻量级锁,也会有性能损失,所以超过阈值后会重偏向,不用再升级为轻量级锁
@Slf4j(topic = "c.TestBiased")
public class TestBiased {
public static void main(String[] args) throws IOException, InterruptedException {
// 线程安全的集合
Vector<Dog> list = new Vector<>();
Thread t1 = new Thread(() -> {
for (int i = 0; i < 30; i++) {
// 创建30个对象,都给t1加偏向锁
Dog d = new Dog();
// 将30个对象加到集合中,为了让t2线程加同样的对象锁
list.add(d);
synchronized (d) {
log.debug(i + "\t" + ClassLayout.parseInstance(d).toPrintable());
}
}
synchronized (list) {
list.notify();
}
}, "t1");
t1.start();
Thread t2 = new Thread(() -> {
synchronized (list) {
try {
list.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
log.debug("===============> ");
for (int i = 0; i < 30; i++) {
// t2线程获取t1线程之前的30个对象锁
Dog d = list.get(i);
log.debug(i + "\t" + ClassLayout.parseInstance(d).toPrintable());
synchronized (d) {
log.debug(i + "\t" + ClassLayout.parseInstance(d).toPrintable());
}
log.debug(i + "\t" + ClassLayout.parseInstance(d).toPrintable());
}
}, "t2");
t2.start();
}
class Dog {
}
[t1] - 0 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 1 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 2 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 3 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 4 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 5 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 6 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 7 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 8 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 9 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 10 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 11 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 12 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 13 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 14 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 15 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 16 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 17 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 18 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 19 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 20 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 21 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 22 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 23 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 24 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 25 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 26 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 27 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 28 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 29 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
// t1 30个对象都是偏向锁,t1线程id为:00000000 00000000 00000000 00000000 00011111 11110011 111000
[t2] - ===============>
// t2加锁前,获取到的对象锁是偏向t1的
[t2] - 0 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
// t2加锁后,撤销偏向锁,升级为轻量级锁 00
[t2] - 0 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000
// t2解锁后,锁状态变为正常状态 001
[t2] - 0 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
[t2] - 1 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 1 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000
[t2] - 1 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
[t2] - 2 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 2 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000
[t2] - 2 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
[t2] - 3 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 3 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000
[t2] - 3 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
[t2] - 4 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 4 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000
[t2] - 4 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
[t2] - 5 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 5 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000
[t2] - 5 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
[t2] - 6 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 6 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000
[t2] - 6 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
[t2] - 7 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 7 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000
[t2] - 7 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
[t2] - 8 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 8 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000
[t2] - 8 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
[t2] - 9 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 9 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000
[t2] - 9 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
[t2] - 10 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 10 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000
[t2] - 10 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
[t2] - 11 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 11 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000
[t2] - 11 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
[t2] - 12 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 12 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000
[t2] - 12 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
[t2] - 13 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 13 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000
[t2] - 13 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
[t2] - 14 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 14 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000
[t2] - 14 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
[t2] - 15 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 15 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000
[t2] - 15 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
[t2] - 16 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 16 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000
[t2] - 16 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
[t2] - 17 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 17 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000
[t2] - 17 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
[t2] - 18 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 18 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000
[t2] - 18 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
// 以上0~18,t2总共撤销了19次
===================================================================================
// 下面从第20次开始,t2第20次加锁前获取的还是偏向t1的锁,t1线程id为:00000000 00000000 00000000 00000000 00011111 11110011 111 000
[t2] - 19 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
// 从第20次的加锁后,准备第20次撤销达到阈值,则开始偏向t2了, t2线程id为:00000000 00000000 00000000 00000000 00011111 11110011 111 100
[t2] - 19 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101
[t2] - 19 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101
[t2] - 20 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 20 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101
[t2] - 20 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101
[t2] - 21 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 21 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101
[t2] - 21 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101
[t2] - 22 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 22 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101
[t2] - 22 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101
[t2] - 23 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 23 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101
[t2] - 23 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101
[t2] - 24 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 24 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101
[t2] - 24 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101
[t2] - 25 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 25 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101
[t2] - 25 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101
[t2] - 26 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 26 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101
[t2] - 26 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101
[t2] - 27 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 27 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101
[t2] - 27 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101
[t2] - 28 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 28 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101
[t2] - 28 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101
[t2] - 29 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 29 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101
[t2] - 29 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101
(5)批量撤销
- 当撤销偏向锁阈值达到 40 次后,jvm 会这样觉得,自己确实偏向错了,根本就不该偏向。于是整个类的所有对象都会变为不可偏向的,新建的对象也是不可偏向的。
@Slf4j(topic = "c.TestBiased")
public class TestBiased {
public static void main(String[] args) throws IOException, InterruptedException {
Vector<Dog> list = new Vector<>();
// 首先让39个对象偏向t1
int loopNumber = 39;
t1 = new Thread(() -> {
for (int i = 0; i < loopNumber; i++) {
Dog d = new Dog();
list.add(d);
synchronized (d) {
log.debug(i + "\t" + ClassLayout.parseInstance(d).toPrintable());
}
}
// 然后再唤醒t2
LockSupport.unpark(t2);
}, "t1");
t1.start();
t2 = new Thread(() -> {
LockSupport.park();
log.debug("===============> ");
for (int i = 0; i < loopNumber; i++) {
Dog d = list.get(i);
log.debug(i + "\t" + ClassLayout.parseInstance(d).toPrintable());
// t2撤销19次t3的偏向锁升级为轻量级锁(撤销19次),第20次撤销后(撤销1次),开始偏向t2
synchronized (d) {
log.debug(i + "\t" + ClassLayout.parseInstance(d).toPrintable());
}
log.debug(i + "\t" + ClassLayout.parseInstance(d).toPrintable());
}
// t2再唤醒t3
LockSupport.unpark(t3);
}, "t2");
t2.start();
t3 = new Thread(() -> {
LockSupport.park();
log.debug("===============> ");
for (int i = 0; i < loopNumber; i++) {
Dog d = list.get(i);
log.debug(i + "\t" + ClassLayout.parseInstance(d).toPrintable());
// 前19个是t2撤销后升级的轻量级锁,从第20个开始,t3撤销t2的偏向锁(撤销20次)
// t2 和 t3 总共撤销了 40次
synchronized (d) {
log.debug(i + "\t" + ClassLayout.parseInstance(d).toPrintable());
}
log.debug(i + "\t" + ClassLayout.parseInstance(d).toPrintable());
}
}, "t3");
t3.start();
// 等待t3执行完
t3.join();
// 撤销了 40次后,整个类的所有对象都变为不可偏向的,新建的对象也是不可偏向的
log.debug(ClassLayout.parseInstance(new Dog()).toPrintable());
}
class Dog {
}
- 第40个对象开始变为不可偏向的状态 001
23:48:39.324 c.TestBiased [main] - cn.itcast.n4.Dog 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) a4 8b 01 20 (10100100 10001011 00000001 00100000) (536972196)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
(6)锁消除
@Fork(1)
@BenchmarkMode(Mode.AverageTime)
@Warmup(iterations=3)
@Measurement(iterations=5)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
public class MyBenchmark {
static int x = 0;
@Benchmark
public void a() {
x++;
}
@Benchmark
// JIT 即时编译器
public void b() {
Object o = new Object();
synchronized (o) {
x++;
}
}
}
默认开启锁消除功能 -XX:+EliminateLocks java -jar benchmarks.jar
Benchmark Mode Samples Score Score error Units
c.i.MyBenchmark.a avgt 5 1.736 0.118 ns/op
c.i.MyBenchmark.b avgt 5 1.649 0.055 ns/op
- 不加锁的a方法 与 加锁的b方法,性能几乎一样
- java运行时,JIT即时编译器会对反复执行的热点代码进行优化,JIT发现b方法的局部变量o,不会逃离b方法的作用范围,不会被共享,所以优化时不会加锁
关闭锁消除功能 -XX:-EliminateLocks java -XX:-EliminateLocks -jar benchmarks.jar
Benchmark Mode Samples Score Score error Units
c.i.MyBenchmark.a avgt 5 1.807 0.215 ns/op
c.i.MyBenchmark.b avgt 5 23.272 4.180 ns/op