conanan's blog conanan's blog
首页
关于
  • 分类
  • 标签
  • 归档
  • Java
  • Java Web
  • 工具

    • Maven
  • MySQL
  • Redis
  • Git
  • Vim
  • Nginx
  • Docker
GitHub

Evan Xu

前端界的小学生
首页
关于
  • 分类
  • 标签
  • 归档
  • Java
  • Java Web
  • 工具

    • Maven
  • MySQL
  • Redis
  • Git
  • Vim
  • Nginx
  • Docker
GitHub
  • 基础

  • API

  • Container

  • 多线程

    • 基本概念
    • 基本使用
    • 线程生命周期
    • 共享模型—管程(悲观锁,阻塞)
    • Monitor & synchronized 原理
      • Monitor—管程/监视器 🔥
        • Java 对象头形式 🔥
        • Monitor 工作原理 🔥
      • synchronized 字节码 🔥
      • 小故事
      • synchronized 原理—轻量级锁 🔥
      • synchronized 原理—锁膨胀 🔥
      • synchronized 原理—自旋优化(优化重量级锁) 🔥
      • synchronized 原理—偏向锁(优化轻量级锁 CAS) 🔥
        • 优化轻量级锁 CAS 🔥
        • 偏向状态
        • 测试延迟性
        • 测试加锁
        • 禁用偏向锁
        • 撤销偏向锁—锁对象调用 hashCode
        • 撤销偏向锁—锁竞争—其他线程使用锁 🔥
        • 撤销偏向锁—锁对象调用 wait/notify
        • 批量重偏向—同一类的对象撤销后第 20 次
        • 批量撤销—同一类对象撤销后第 40 次
      • synchronized 原理—锁消除 🔥
      • synchronized 原理—锁粗化
    • 应用
    • 设计模式
    • 8 IO
    • 9 NIO
    • 10 AIO
    • 11 Network
    • 2 Object Orientation-4 注解
    • 12 Reflect
  • 16 9,10,11新特性
  • 17 Test
  • 18 设计原则&设计模式
  • JDBC
  • Java
  • 多线程
conanan
2021-02-19

Monitor & synchronized 原理

# Monitor & synchronized 原理

# Monitor—管程/监视器 🔥

# Java 对象头形式 🔥

参考资料

  • 简书

  • markOop.hpp

  • CompressedOops

  • JVM 优化之压缩普通对象指针

  • What is in java object header

由于 Java 面向对象的思想,在 JVM 中需要大量存储对象,存储时为了实现一些额外的功能,需要在对象中添加一些标记字段用于增强对象功能,这些标记字段组成了对象头。

JVM 中对象头的方式有以下两种,以 32 位虚拟机为例

普通对象(包括 2 个部分):

|--------------------------------------------------------------|
|                     Object Header (64 bits)                  |
|------------------------------------|-------------------------|
|        Mark Word (32 bits)         |    Klass Word (32 bits) |
|------------------------------------|-------------------------|
1
2
3
4
5

由此可得出,int 占用 4 字节,Integer 占用 4+8 = 12 字节

数组对象(包括 3 个部分)

|---------------------------------------------------------------------------------|
|                                 Object Header (96 bits)                         |
|--------------------------------|-----------------------|------------------------|
|        Mark Word(32bits)       |    Klass Word(32bits) |  array length(32bits)  |
|--------------------------------|-----------------------|------------------------|
1
2
3
4
5

# Mark Word 🔥

这部分主要用来存储对象自身的运行时数据,如 hashcode、gc 分代年龄等。mark word的位长度为 JVM 的一个 Word 大小,也就是说 32 位 JVM 的Mark word为 32 位,64 位 JVM 为 64 位。 为了让一个字大小存储更多的信息,JVM 将字的最低两个位设置为标记位,不同标记位下的 Mark Word 示意如下:

|-------------------------------------------------------|--------------------|
|                  Mark Word (32 bits)                  |       State        |
|-------------------------------------------------------|--------------------|
| identity_hashcode:25 | age:4 | biased_lock:1 | lock:2 |       Normal       |
|-------------------------------------------------------|--------------------|
|  thread:23 | epoch:2 | age:4 | biased_lock:1 | lock:2 |       Biased       |
|-------------------------------------------------------|--------------------|
|               ptr_to_lock_record:30          | lock:2 | Lightweight Locked |
|-------------------------------------------------------|--------------------|
|               ptr_to_heavyweight_monitor:30  | lock:2 | Heavyweight Locked |
|-------------------------------------------------------|--------------------|
|                                              | lock:2 |    Marked for GC   |
|-------------------------------------------------------|--------------------|
1
2
3
4
5
6
7
8
9
10
11
12
13

lock:2 位的锁状态标记位,由于希望用尽可能少的二进制位表示尽可能多的信息,所以设置了 lock 标记。该标记的值不同,整个 mark word 表示的含义不同。

biased_lock lock 状态
0 01 无锁
1 01 偏向锁
0 00 轻量级锁
0 10 重量级锁
0 11 GC 标记

biased_lock:对象是否启用偏向锁标记,只占 1 个二进制位。为 1 时表示对象启用偏向锁,为 0 时表示对象没有偏向锁。 age:4 位的 Java 对象年龄。在 GC 中,如果对象在 Survivor 区复制一次,年龄增加 1。当对象达到设定的阈值时,将会晋升到老年代。默认情况下,并行 GC 的年龄阈值为 15,并发 GC 的年龄阈值为 6。由于 age 只有 4 位,所以最大值为 15,这就是-XX:MaxTenuringThreshold选项最大值为 15 的原因。 identity_hashcode:25 位的对象标识 Hash 码,采用延迟加载技术。调用方法System.identityHashCode()计算,并会将结果写到该对象头中。当对象被锁定时,该值会移动到管程 Monitor 中。 thread:持有偏向锁的线程 ID。 epoch:偏向时间戳。 ptr_to_lock_record:指向栈中锁记录的指针。 ptr_to_heavyweight_monitor:指向管程 Monitor 的指针。

64 位下的标记字与 32 位的相似:

|------------------------------------------------------------------------------|--------------------|
|                                  Mark Word (64 bits)                         |       State        |
|------------------------------------------------------------------------------|--------------------|
| unused:25 | identity_hashcode:31 | unused:1 | age:4 | biased_lock:1 | lock:2 |       Normal       |
|------------------------------------------------------------------------------|--------------------|
| thread:54 |       epoch:2        | unused:1 | age:4 | biased_lock:1 | lock:2 |       Biased       |
|------------------------------------------------------------------------------|--------------------|
|                       ptr_to_lock_record:62                         | lock:2 | Lightweight Locked |
|------------------------------------------------------------------------------|--------------------|
|                     ptr_to_heavyweight_monitor:62                   | lock:2 | Heavyweight Locked |
|------------------------------------------------------------------------------|--------------------|
|                                                                     | lock:2 |    Marked for GC   |
|------------------------------------------------------------------------------|--------------------|
1
2
3
4
5
6
7
8
9
10
11
12
13

# Klass Word

这一部分用于存储对象的类型指针,该指针指向它的类元数据,JVM 通过这个指针确定对象是哪个类的实例。该指针的位长度为 JVM 的一个字大小,即 32 位的 JVM 为 32 位,64 位的 JVM 为 64 位。 如果应用的对象过多,使用 64 位的指针将浪费大量内存,统计而言,64 位的 JVM 将会比 32 位的 JVM 多耗费 50%的内存。为了节约内存可以使用选项+UseCompressedOops开启指针压缩,其中,oop 即 ordinary object pointer 普通对象指针。开启该选项后,下列指针将压缩至 32 位:

  1. 每个 Class 的属性指针(即静态变量)
  2. 每个对象的属性指针(即对象变量)
  3. 普通对象数组的每个元素指针

当然,也不是所有的指针都会压缩,一些特殊类型的指针 JVM 不会优化,比如指向 PermGen 的 Class 对象指针(JDK8 中指向元空间的 Class 对象指针)、本地变量、堆栈元素、入参、返回值和 NULL 指针等。

# array length

如果对象是一个数组,那么对象头还需要有额外的空间用于存储数组的长度,这部分数据的长度也随着 JVM 架构的不同而不同:32 位的 JVM 上,长度为 32 位;64 位 JVM 则为 64 位。64 位 JVM 如果开启+UseCompressedOops选项,该区域长度也将由 64 位压缩至 32 位。

# Monitor 工作原理 🔥

Monitor 被翻译为监视器或管程,OS 中的概念。每个 Java 对象(此时可以称为锁对象)都可以关联一个 Monitor 对象,如果使用 synchronized 给对象上锁(重量级)之后,该对象头的 Mark Word 中就被设置指向 Monitor 对象的指针

  • 刚开始 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 中等待的线程来竞争锁,竞争的时是非公平的
  • 图中 WaitSet 中的 Thread-0,Thread-1 是之前获得过锁,但条件不满足进入 WAITING 状态的线程,后面讲 wait-notify 时会分析

注意

  • synchronized 必须是进入同一个对象(同一把锁)的 monitor 才有上述的效果。

  • 不加 synchronized 的对象不会关联监视器,不遵从以上规则

# synchronized 字节码 🔥

public class Test {
    static final Object lock = new Object();
    static int counter = 0;
    public static void main(String[] args) {
        synchronized (lock) {
            counter++;
        }
    }
}
1
2
3
4
5
6
7
8
9

其对应的字节码如下:

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引用 (synchronized开始)
            3: dup // 复制一份
            4: astore_1 // lock引用 -> slot 1。保存到slot1变量槽中,为了解锁
            5: monitorenter // 将 lock对象 MarkWord 置为 Monitor 指针,这里底层是c调用。这里就是synchronized代码了
            6: getstatic #3 // <- i
            9: iconst_1 // 准备常数 1
            10: iadd // +1
            11: putstatic #3 // -> i
            14: aload_1 // <- lock引用。拿到该引用
            15: monitorexit // 将 lock对象 MarkWord 重置, 唤醒 EntryList
            16: goto 24
            // 锁中异常,将锁释放
            19: astore_2 // e -> slot 2
            20: aload_1 // <- lock引用
            21: monitorexit // 将 lock对象 MarkWord 重置(有hashcode等等), 唤醒 EntryList
            22: aload_2 // <- slot 2 (e)
            23: athrow // throw e
            // 锁中异常,将锁释放
            24: return
        Exception table:
			from to target type
			6 16 19 any
			19 22 19 any
		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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43

注意

方法级别的 synchronized 不会在字节码指令中有所体现

# 小故事

JDK 从 6 开始对锁进行了改进

故事角色

  • 老王 - JVM

  • 小南 - 线程

  • 小女 - 线程

  • 房间 - 对象

  • 房间门上 - 防盗锁 - Monitor

  • 房间门上 - 小南书包 - 轻量级锁

  • 房间门上 - 刻上小南大名 - 偏向锁

  • 批量重刻名 - 一个类的偏向锁撤销到达 20 阈值

  • 不能刻名字 - 批量撤销该类对象的偏向锁,设置该类不可偏向

# synchronized 原理—轻量级锁 🔥

轻量级锁的使用场景:如果一个对象虽然有多线程要加锁,但加锁的时间是错开的(也就是没有竞争),那么可以使用轻量级锁来优化。

轻量级锁对使用者是透明的,即语法仍然是 synchronized

假设有两个方法同步块,利用同一个对象加锁

static final Object obj = new Object();
public static void method1() {
    synchronized( obj ) {
        // 同步块 A
        method2();
    }
}
public static void method2() {
    synchronized( obj ) {
        // 同步块 B
    }
}
1
2
3
4
5
6
7
8
9
10
11
12

# synchronized 原理—锁膨胀 🔥

如果在尝试加轻量级锁的过程中,CAS 操作无法成功,这时一种情况就是有其它线程为此对象加上了轻量级锁(有竞争),这时需要进行锁膨胀,将轻量级锁变为重量级锁(让解锁操作接下来的解锁操作进入到重量级锁的解锁)。

public class Test {
    static Object obj = new Object();
    public static void method1() {
        synchronized( obj ) {
            // 同步块
        }
    }
}
1
2
3
4
5
6
7
8

# synchronized 原理—自旋优化(优化重量级锁) 🔥

重量级锁竞争的时候,还可以使用自旋来进行优化,如果当前线程自旋成功(即这时候持锁线程已经退出了同步块,释放了锁),这时当前线程就可以避免阻塞,避免线程上下文切换。

线程切换

线程的阻塞唤醒需要从用户态切换到内核态,然后内核态切换 tcb,切到另一个线程的内核态,再从内核态进入用户态,这是一个重量级的操作

自旋重试成功的情况:

线程 1(core1 上) 对象 Mark 线程 2(core2 上)
- 10(重量锁) -
访问同步块,获取 monitor 10(重量锁)重量锁指针 -
成功(加锁) 10(重量锁)重量锁指针 -
执行同步块 10(重量锁)重量锁指针 -
执行同步块 10(重量锁)重量锁指针 访问同步块,获取 monitor
执行同步块 10(重量锁)重量锁指针 自旋重试
执行完毕 10(重量锁)重量锁指针 自旋重试
成功(解锁) 01(无锁) 自旋重试
- 10(重量锁)重量锁指针 成功(加锁)
- 10(重量锁)重量锁指针 执行同步块
- ... ...

自旋重试失败的情况:

线程 1(core1 上) 对象 Mark 线程 2(core2 上)
- 10(重量锁) -
访问同步块,获取 monitor 10(重量锁)重量锁指针 -
成功(加锁) 10(重量锁)重量锁指针 -
执行同步块 10(重量锁)重量锁指针 -
执行同步块 10(重量锁)重量锁指针 访问同步块,获取 monitor
执行同步块 10(重量锁)重量锁指针 自旋重试
执行同步块 10(重量锁)重量锁指针 自旋重试
执行同步块 10(重量锁)重量锁指针 自旋重试
执行同步块 10(重量锁)重量锁指针 阻塞
- ... ...
  • 自旋会占用 CPU 时间,单核 CPU 自旋就是浪费,多核 CPU 自旋才能发挥优势。
  • 在 Java 6 之后自旋锁是自适应的,比如对象刚刚的一次自旋操作成功过,那么认为这次自旋成功的可能性会高,就多自旋几次;反之,就少自旋甚至不自旋,总之,比较智能。
  • Java 7 之后不能控制是否开启自旋功能

# synchronized 原理—偏向锁(优化轻量级锁 CAS) 🔥

# 优化轻量级锁 CAS 🔥

轻量级锁在没有竞争时(就自己这个线程),每次重入仍然需要执行 CAS 操作(还需再次记录 lock record)。

Java 6 中引入了偏向锁来做进一步优化:只有第一次使用 CAS 时就将线程 ID 设置到对象的 Mark Word 头,之后发现这个线程 ID 是自己的就表示没有竞争,不用重新 CAS。以后只要不发生竞争,这个对象就归该线程所有

例如:

public class Test {
    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
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

# 偏向状态

回忆一下对象头格式

|------------------------------------------------------------------------------|--------------------|
|                                  Mark Word (64 bits)                         |       State        |
|------------------------------------------------------------------------------|--------------------|
| unused:25 | identity_hashcode:31 | unused:1 | age:4 | biased_lock:1 | lock:2 |       Normal       |
|------------------------------------------------------------------------------|--------------------|
| thread:54 |       epoch:2        | unused:1 | age:4 | biased_lock:1 | lock:2 |       Biased       |
|------------------------------------------------------------------------------|--------------------|
|                       ptr_to_lock_record:62                         | lock:2 | Lightweight Locked |
|------------------------------------------------------------------------------|--------------------|
|                     ptr_to_heavyweight_monitor:62                   | lock:2 | Heavyweight Locked |
|------------------------------------------------------------------------------|--------------------|
|                                                                     | lock:2 |    Marked for GC   |
|------------------------------------------------------------------------------|--------------------|
1
2
3
4
5
6
7
8
9
10
11
12
13

一个对象创建时:

  • 如果开启了偏向锁(默认开启),那么对象创建后,markword 值为 0x05 即最后 3 位为 101,这时它的 thread、epoch、age 都为 0,加锁时才会改变

  • 偏向锁是默认是延迟的,不会在程序启动时立即生效,如果想避免延迟,可以加 VM 参数

    -XX:BiasedLockingStartupDelay=0 来禁用延迟

  • 如果没有开启偏向锁,那么对象创建后,Mark Word 值为 0x01 即最后 3 位为 001,这时它的 hashcode、age 都为 0,第一次用到 hashcode 时才会赋值

查看 Java 对象头需要借助 openjdk 提供的工具

<!-- https://mvnrepository.com/artifact/org.openjdk.jol/jol-core -->
<dependency>
    <groupId>org.openjdk.jol</groupId>
    <artifactId>jol-core</artifactId>
    <version>0.14</version>
    <scope>provided</scope>
</dependency>
1
2
3
4
5
6
7

# 测试延迟性

@Slf4j(topic = "TestBiasedLock")
public class TestBiasedLock {

    public static void main(String[] args) throws InterruptedException {
        test1();
    }

    /**
     * 测试101
     */
    private static void test1(){
        log.debug(ClassLayout.parseInstance(new Dog()).toPrintable());

        // 偏向锁是默认是延迟的,不会在程序启动时立即生效。VM参数可以解决-XX:BiasedLockingStartupDelay=0
        // 或睡一会。TimeUnit.SECONDS.sleep(5);
        log.debug(ClassLayout.parseInstance(new Dog()).toPrintable());
    }
}

class Dog {

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

结果如下(从左往右,从上到下都是倒排的):

21:21:16.604 [main] DEBUG TestBiasedLock - _999_demo.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)                           22 dc 00 f8 (00100010 11011100 00000000 11111000) (-134161374)
     12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
1
2
3
4
5
6
7
8

# 测试加锁

@Slf4j(topic = "TestBiasedLock")
public class TestBiasedLock {

    public static void main(String[] args) throws InterruptedException {
        test2();
    }

    private static void test2() {
        Dog d = new Dog();
        ClassLayout classLayout = ClassLayout.parseInstance(d);
        new Thread(() -> {
            log.debug("synchronized 加锁前\n{}", classLayout.toPrintable());
            synchronized (d) {
                log.debug("synchronized 加锁中\n{}", classLayout.toPrintable());
            }
            log.debug("synchronized 加锁后\n{}",classLayout.toPrintable());
        }, "t1").start();
    }
}

class Dog {

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

结果如下(从左往右,从上到下都是倒排的,同一字节中是按照正常顺序排列):

21:22:16.638 [t1] DEBUG TestBiasedLock - synchronized 加锁前
_999_demo.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)                           22 dc 00 f8 (00100010 11011100 00000000 11111000) (-134161374)
     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:22:16.643 [t1] DEBUG TestBiasedLock - synchronized 加锁中
_999_demo.Dog object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           05 b8 19 86 (00000101 10111000 00011001 10000110) (-2045134843)
      4     4        (object header)                           a4 7f 00 00 (10100100 01111111 00000000 00000000) (32676)
      8     4        (object header)                           22 dc 00 f8 (00100010 11011100 00000000 11111000) (-134161374)
     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:22:16.644 [t1] DEBUG TestBiasedLock - synchronized 加锁后
_999_demo.Dog object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           05 b8 19 86 (00000101 10111000 00011001 10000110) (-2045134843)
      4     4        (object header)                           a4 7f 00 00 (10100100 01111111 00000000 00000000) (32676)
      8     4        (object header)                           22 dc 00 f8 (00100010 11011100 00000000 11111000) (-134161374)
     12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

其中 00000000 00000000 01111111 10100100 10000110 00011001 100111 是线程 id(OS 分配的,和 Java 不一样)。只有第二次及之后才有。处于偏向锁的对象解锁后,线程 id 仍存储于对象头中

# 禁用偏向锁

偏向锁适用于,竞争非常少的情况。当应用是一个多线程非常多的,此时应该禁用掉

添加 VM 参数 -XX:-UseBiasedLocking 禁用偏向锁。因为变量前是 - 所以关闭了,若是+则开启

结果如下(从左往右,从上到下都是倒排的,同一字节中是按照正常顺序排列):

01:57:17.769 [t1] DEBUG TestBiasedLock - synchronized 加锁前
_999_demo.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)                           22 dc 00 f8 (00100010 11011100 00000000 11111000) (-134161374)
     12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

01:57:17.775 [t1] DEBUG TestBiasedLock - synchronized 加锁中
_999_demo.Dog object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           00 b9 6d 06 (00000000 10111001 01101101 00000110) (107854080)
      4     4        (object header)                           00 70 00 00 (00000000 01110000 00000000 00000000) (28672)
      8     4        (object header)                           22 dc 00 f8 (00100010 11011100 00000000 11111000) (-134161374)
     12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

01:57:17.776 [t1] DEBUG TestBiasedLock - synchronized 加锁后
_999_demo.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)                           22 dc 00 f8 (00100010 11011100 00000000 11111000) (-134161374)
     12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

可以看到,加锁中最后为000即轻量锁。加锁前后为001即无锁(除了偏向锁其他锁执行完后都是001无锁状态)

# 撤销偏向锁—锁对象调用 hashCode

调用了对象的 hashCode,但偏向锁的对象 MarkWord 中存储的是线程 id,如果调用 hashCode 会导致偏向锁被撤销(不够存线程 ID),升级为轻量锁。

  • 轻量级锁的 hashCode 会在无锁 CAS 转为轻量锁时记录到锁记录中

  • 重量级锁会在 Monitor 中记录 hashCode

测试在调用 hashCode 后使用偏向锁,记得去掉 -XX:-UseBiasedLocking,加上

-XX:BiasedLockingStartupDelay=0

@Slf4j(topic = "TestBiasedLock")
public class TestBiasedLock {

    public static void main(String[] args) throws InterruptedException {
        test2();
    }

    private static void test2() {
        Dog d = new Dog();
        d.hashCode();
        ClassLayout classLayout = ClassLayout.parseInstance(d);
        new Thread(() -> {
            log.debug("synchronized 加锁前\n{}", classLayout.toPrintable());
            synchronized (d) {
                log.debug("synchronized 加锁中\n{}", classLayout.toPrintable());
            }
            log.debug("synchronized 加锁后\n{}",classLayout.toPrintable());
        }, "t1").start();
    }
}

class Dog {

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

结果如下(从左往右,从上到下都是倒排的,同一字节中是按照正常顺序排列):

21:25:59.886 [t1] DEBUG TestBiasedLock - synchronized 加锁前
_999_demo.Dog object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           01 18 94 ad (00000001 00011000 10010100 10101101) (-1382803455)
      4     4        (object header)                           20 00 00 00 (00100000 00000000 00000000 00000000) (32)
      8     4        (object header)                           22 de 00 f8 (00100010 11011110 00000000 11111000) (-134160862)
     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:25:59.890 [t1] DEBUG TestBiasedLock - synchronized 加锁中
_999_demo.Dog object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           00 b9 c5 03 (00000000 10111001 11000101 00000011) (63289600)
      4     4        (object header)                           00 70 00 00 (00000000 01110000 00000000 00000000) (28672)
      8     4        (object header)                           22 de 00 f8 (00100010 11011110 00000000 11111000) (-134160862)
     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:25:59.890 [t1] DEBUG TestBiasedLock - synchronized 加锁后
_999_demo.Dog object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           01 18 94 ad (00000001 00011000 10010100 10101101) (-1382803455)
      4     4        (object header)                           20 00 00 00 (00100000 00000000 00000000 00000000) (32)
      8     4        (object header)                           22 de 00 f8 (00100010 11011110 00000000 11111000) (-134160862)
     12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

加锁前后都是001即无锁,加锁中为000轻量锁,轻量锁执行完后都是001无锁状态

# 撤销偏向锁—锁竞争—其他线程使用锁 🔥

当有其它线程使用偏向锁对象时,会将偏向锁升级为轻量级锁。因为偏向锁本来是让该线程一直使用的,但是发生竞争,需升级为轻量锁

需要控制线程交替执行!

@Slf4j(topic = "TestBiasedLock")
public class TestBiasedLock {

    public static void main(String[] args) {
        test3();
    }

    /**
     * 其他线程使用对象 撤销偏向锁
     */
    private static void test3() {
        Dog d = new Dog();
        ClassLayout classLayout = ClassLayout.parseInstance(d);
        Thread t1 = new Thread(() -> {
            log.debug("t1 synchronized 加锁前\n{}", classLayout.toPrintable());
            synchronized (d) {
                log.debug("t1 synchronized 加锁中\n{}", classLayout.toPrintable());
            }
            log.debug("t1 synchronized 加锁后\n{}", classLayout.toPrintable());

            // 这里和 wait、notify 撤销偏向锁不一样哦,因为锁对象都不一样!
            // t1 执行完再唤醒 t2(用同一把锁)。此时wait、notify不是重量锁
            synchronized (TestBiasedLock.class) {
                TestBiasedLock.class.notify();
            }

        }, "t1");
        t1.start();

        // 结果是要让t2先执行哦,可以放到t1前
        Thread t2 = new Thread(() -> {
            // 这里和 wait、notify 撤销偏向锁不一样哦,因为锁对象都不一样!
            // 等待唤醒
            synchronized (TestBiasedLock.class) {
                try {
                    TestBiasedLock.class.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

            log.debug("t2 synchronized 加锁前\n{}", classLayout.toPrintable());
            synchronized (d) {
                log.debug("t2 synchronized 加锁中\n{}", classLayout.toPrintable());
            }
            log.debug("t2 synchronized 加锁后\n{}", classLayout.toPrintable());
        }, "t2");
        t2.start();
    }
}

class Dog {

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54

结果如下(从左往右,从上到下都是倒排的,同一字节中是按照正常顺序排列):

21:20:40.564 [t1] DEBUG TestBiasedLock - t1 synchronized 加锁前
_999_demo.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)                           22 dc 00 f8 (00100010 11011100 00000000 11111000) (-134161374)
     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:20:40.569 [t1] DEBUG TestBiasedLock - t1 synchronized 加锁中
_999_demo.Dog object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           05 08 17 cc (00000101 00001000 00010111 11001100) (-870905851)
      4     4        (object header)                           cf 7f 00 00 (11001111 01111111 00000000 00000000) (32719)
      8     4        (object header)                           22 dc 00 f8 (00100010 11011100 00000000 11111000) (-134161374)
     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:20:40.570 [t1] DEBUG TestBiasedLock - t1 synchronized 加锁后
_999_demo.Dog object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           05 08 17 cc (00000101 00001000 00010111 11001100) (-870905851)
      4     4        (object header)                           cf 7f 00 00 (11001111 01111111 00000000 00000000) (32719)
      8     4        (object header)                           22 dc 00 f8 (00100010 11011100 00000000 11111000) (-134161374)
     12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

由上面状态 101、101、101、101、000、001 改变可以得出 t1 线程都是偏向锁,t2 线程加锁前、中、后分别为偏向锁、轻量锁(竞争导致锁升级)、无锁。轻量锁执行完后都是001无锁状态

# 撤销偏向锁—锁对象调用 wait/notify

wait/notify 机制只有重量级锁才有!只要调用了就变为重量级锁

@Slf4j(topic = "TestBiasedLock")
public class TestBiasedLock {

    public static void main(String[] args) {
        test4();
    }

    /**
     * 撤销偏向锁—锁对象调用 wait/notify
     */
    private static void test4() {
        Dog d = new Dog();
        ClassLayout classLayout = ClassLayout.parseInstance(d);

        // 结果是要让t1先执行哦
        Thread t1 = new Thread(() -> {
            log.debug("t1 synchronized 加锁前\n{}", classLayout.toPrintable());
            synchronized (d) {
                log.debug("t1 synchronized 加锁中\n{}", classLayout.toPrintable());
                try {
                    // 等待
                    d.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            log.debug("t1 synchronized 加锁后\n{}", classLayout.toPrintable());
        }, "t1");
        t1.start();


        Thread t2 = new Thread(() -> {
            try {
                TimeUnit.SECONDS.sleep(5);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            synchronized (d){
                log.debug("t2 notify");
                d.notify();
            }

        }, "t2");
        t2.start();
    }
}

class Dog {

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51

结果如下(从左往右,从上到下都是倒排的,同一字节中是按照正常顺序排列):

21:48:53.705 [t1] DEBUG TestBiasedLock - t1 synchronized 加锁前
_999_demo.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)                           22 dc 00 f8 (00100010 11011100 00000000 11111000) (-134161374)
     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:48:53.710 [t1] DEBUG TestBiasedLock - t1 synchronized 加锁中
_999_demo.Dog object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           05 08 83 5e (00000101 00001000 10000011 01011110) (1585645573)
      4     4        (object header)                           f9 7f 00 00 (11111001 01111111 00000000 00000000) (32761)
      8     4        (object header)                           22 dc 00 f8 (00100010 11011100 00000000 11111000) (-134161374)
     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:48:58.703 [t2] DEBUG TestBiasedLock - t2 notify
21:48:58.706 [t1] DEBUG TestBiasedLock - t1 synchronized notify 后
_999_demo.Dog object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           fa 93 01 5e (11111010 10010011 00000001 01011110) (1577161722)
      4     4        (object header)                           f9 7f 00 00 (11111001 01111111 00000000 00000000) (32761)
      8     4        (object header)                           22 dc 00 f8 (00100010 11011100 00000000 11111000) (-134161374)
     12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30

上面的状态:加锁前 101 偏向锁、加锁中 101 偏向锁、nofity 后 010 重量级锁(锁膨胀),重量锁不会撤销标记、因为重量级锁是根据 monitor 的 owner 来判断锁状态的

# 批量重偏向—同一类的对象撤销后第 20 次

都是撤销操作阈值

如果对象虽然被多个线程访问,但没有竞争,这时偏向了线程 T1 的对象仍有机会重新偏向 T2,重偏向会重置对象的 Thread ID。当撤销偏向锁阈值超过 20 次后,jvm 会这样觉得,我是不是偏向错了呢,于是会在给这些对象加锁时不会撤销锁而是重新偏向至加锁线程。没超过阈值前,由于锁竞争会升级为轻量级锁,最终执行完后清空 Thread ID,并将锁状态置为001即无锁状态

@Slf4j(topic = "TestBiasedLock")
public class TestBiasedLock {

    public static void main(String[] args) {
        test5();
    }

    /**
     * 批量重偏向
     */
    private static void test5() {
        Vector<Dog> list = new Vector<>();
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 30; i++) {
                Dog d = new Dog();
                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++) {
                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 {

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51

结果如下(老师简化后的工具类)

[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
[t2] - ===============>
[t2] - 0 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 0 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000
[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
[t2] - 19 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121

# 批量撤销—同一类对象撤销后第 40 次

都是撤销操作阈值

当撤销偏向锁阈值超过 40 次后,jvm 会这样觉得,自己确实偏向错了,根本就不该偏向。于是整个类的所有对象都会变为不可偏向的,新建的对象也是不可偏向的

@Slf4j(topic = "TestBiasedLock")
public class TestBiasedLock {

    public static void main(String[] args) throws InterruptedException {
        test6();
    }

    static Thread t1, t2, t3;

    /**
     * 批量撤销
     */
    private static void test6() throws InterruptedException {

        Vector<Dog> list = new Vector<>();
        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());
                }
            }
            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());
                synchronized (d) {
                    log.debug(i + "\t" + ClassLayout.parseInstance(d).toPrintable());
                }
                log.debug(i + "\t" + ClassLayout.parseInstance(d).toPrintable());
            }
            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());
                synchronized (d) {
                    log.debug(i + "\t" + ClassLayout.parseInstance(d).toPrintable());
                }
                log.debug(i + "\t" + ClassLayout.parseInstance(d).toPrintable());
            }
        }, "t3");
        t3.start();

        t3.join();
        log.debug(ClassLayout.parseInstance(new Dog()).toPrintable());

    }
}

class Dog {

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66

结果如下:略

  • 第一次 39 个加 t1 的偏向锁
  • 第二次前19个会发生锁竞争,最终撤销锁变为无锁状态,第 20 个开始发生批量重偏向,最终锁状态都是指向 t2 的偏向锁
  • 第三次前 19 个无锁变为偏向锁,没啥说的。第 20 个开始锁竞争,会撤销锁,即 20 次
  • 那么这个类的对象有了 39 次锁撤销,那么第 40 次以及之后创建该对象,则该对象默认为无锁状态

# synchronized 原理—锁消除 🔥

benchmarks 项目见前面章节

@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() throws Exception {
        x++;
    }

    // JIT(即时编译器)对Java字节码进行进一步优化
    // 发现锁对象就是局部变量,不会逃离作用范围,所以去掉该锁!这里面的x变量也是局部变量,不会共享,所以去掉该锁!
    @Benchmark
    public void b() throws Exception {
        Object o = new Object();
        synchronized (o) {
            x++;
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
java -jar benchmarks.jar

Benchmark Mode Samples Score Score error Units
c.i.MyBenchmark.a avgt 5 1.542 0.056 ns/op
c.i.MyBenchmark.b avgt 5 1.518 0.091 ns/op
1
2
3
4
5

禁用锁消除优化

java -XX:-EliminateLocks -jar benchmarks.jar
Benchmark Mode Samples Score Score error Units
c.i.MyBenchmark.a avgt 5 1.507 0.108 ns/op
c.i.MyBenchmark.b avgt 5 16.976 1.572 ns/op
1
2
3
4

# synchronized 原理—锁粗化

对相同对象多次加锁,导致线程发生多次重入,可以使用锁粗化方式来优化,这不同于之前讲的细分锁的粒度。

编辑
上次更新: 2021/07/07, 14:41:01
共享模型—管程(悲观锁,阻塞)
应用

← 共享模型—管程(悲观锁,阻塞) 应用→

最近更新
01
线程生命周期
07-06
02
线程安全理论
06-24
03
并发简史
06-24
更多文章>
Theme by Vdoing | Copyright © 2019-2021 Evan Xu | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式
×