全面深入学习Java并发编程-第四章

142 阅读32分钟

一、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