• 剑指JUC原理-5.synchronized底层原理


    • 👏作者简介:大家好,我是爱吃芝士的土豆倪,24届校招生Java选手,很高兴认识大家
    • 📕系列专栏:Spring源码、JUC源码
    • 🔥如果感觉博主的文章还不错的话,请👍三连支持👍一下博主哦
    • 🍂博主正在努力完成2023计划中:源码溯源,一探究竟
    • 📝联系方式:nhs19990716,加我进群,大家一起学习,一起进步,一起对抗互联网寒冬👀

    Java对象头

    以32位虚拟机为例:

    普通对象

    在这里插入图片描述

    Java虚拟机中,每个对象都有一个对象头(Object Header),其中包含了一些用于管理对象的元数据信息。对象头通常由两部分组成:mark word(标记字)和klass word(类指针字)。

    Mark Word(标记字):Mark Word是用于存储对象的运行时数据和锁相关的信息。它的具体结构和含义可能因不同的虚拟机实现而有所差异,但通常包含以下信息:

    • 对象的哈希码(Hash Code):用于快速比较对象是否相等。
    • GC相关信息:标记对象是否被垃圾回收器标记为可回收、是否被锁定等。
    • 锁状态:用于支持对象的同步机制,如偏向锁、轻量级锁、重量级锁等。
    • 并发标记:用于并发垃圾回收算法中的标记过程。

    Klass Word(类指针字):Klass Word是指向对象所属类的指针。它指向对象的类元数据(Class Metadata),包含了类的方法、字段、父类、接口等信息。通过Klass Word,可以确定对象的类型,并进行动态分派,即在运行时根据对象的实际类型调用相应的方法。

    对象头中的mark word和klass word在Java虚拟机中起着重要的作用,用于管理对象的状态、锁定机制和类型信息等。它们是实现Java语言特性和虚拟机功能的关键元素。

    通过看对象头的位数,占8个字节

    其中int占 4 个字节,Integer 占用4个字节 + 8个字节的对象头,一个Integer大int三倍,所以在内存很敏感的场景,建议使用int。

    Mark Word结构

    在这里插入图片描述

    这个部分后面再详细讲解,这里其实可以理解为各种锁的状态

    Monitor原理

    Monitor 被翻译为监视器管程

    每个 Java 对象都可以关联一个 Monitor 对象,如果使用 synchronized 给对象上锁(重量级)之后,该对象头的
    Mark Word 中就被设置指向 Monitor 对象的指针

    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 )的 monitor 才有上述的效果
    • 不加 synchronized 的对象不会关联监视器,不遵从以上规则

    synchronized 原理

    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

    对应的字节码为:

    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开始)拿到lock锁
     3: dup			// 复制一份
     4: astore_1 // lock引用 -> slot 1 将其存储到临时变量slot1里面,以后解锁时使用
     5: monitorenter // 将 lock对象 MarkWord 置为 Monitor 指针
     6: getstatic #3 // <- i
     9: iconst_1 // 准备常数 1
     10: iadd // +1
     11: putstatic #3 // -> i     6 9 10 11 做的是i++操作
     14: aload_1 // <- lock引用	拿到slot1中临时的引用地址了
     15: monitorexit // 将 lock对象 MarkWord 重置, 唤醒 EntryList
     16: goto 24	// 执行到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		而19~23涉及到 发生异常时 锁时如何释放的
     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

    从头开始读 这段字节码:本质上就是获取锁,保存引用,然后讲锁对象的markword与monitor关联,然后执行锁中的内容,最终锁内容结束,释放并唤醒EntryList中的其他线程。

    synchronized进阶小故事

    • 老王 - JVM
    • 小南 - 线程
    • 小女 - 线程
    • 房间 - 对象
    • 房间门上 - 防盗锁 - Monitor
    • 房间门上 - 小南书包 - 轻量级锁
    • 房间门上 - 刻上小南大名 - 偏向锁
    • 批量重刻名 - 一个类的偏向锁撤销到达 20 阈值
    • 不能刻名字 - 批量撤销该类对象的偏向锁,设置该类不可偏向

    小南要使用房间保证计算不被其它人干扰(原子性),最初,他用的是防盗锁(monitor),当上下文切换时,锁住门。这样,
    即使他离开了,别人也进不了门,他的工作就是安全的。

    但是,很多情况下没人跟他来竞争房间的使用权。小女是要用房间,但使用的时间上是错开的,小南白天用,小女
    晚上用。每次上锁太麻烦了,有没有更简单的办法呢?

    小南和小女商量了一下,约定不锁门了,而是谁用房间,谁把自己的书包挂在门口,但他们的书包样式都一样,因
    此每次进门前得翻翻书包,看课本是谁的,如果是自己的,那么就可以进门,这样省的上锁解锁了。万一书包不是
    自己的,那么就在门外等,并通知对方下次用锁门的方式。

    后来,小女回老家了,很长一段时间都不会用这个房间。小南每次还是挂书包,翻书包,虽然比锁门省事了,但仍
    然觉得麻烦。

    于是,小南干脆在门上刻上了自己的名字:【小南专属房间,其它人勿用】,下次来用房间时,只要名字还在,那
    么说明没人打扰,还是可以安全地使用房间。如果这期间有其它人要用这个房间,那么由使用者将小南刻的名字擦
    掉,升级为挂书包的方式。

    同学们都放假回老家了,小南就膨胀了,在 20 个房间刻上了自己的名字,想进哪个进哪个。后来他自己放假回老
    家了,这时小女回来了(她也要用这些房间),结果就是得一个个地擦掉小南刻的名字,升级为挂书包的方式。老
    王觉得这成本有点高,提出了一种批量重刻名的方法,他让小女不用挂书包了,可以直接在门上刻上自己的名字

    后来,刻名的现象越来越频繁,老王受不了了:算了,这些房间都不能刻名了,只能挂书包

    synchronized 原理进阶

    轻量级锁

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

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

    static final Object obj = new Object();
    public static void method1() {
     synchronized( obj ) {
    
     }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    创建锁记录(Lock Record)对象,每个线程的栈帧都会包含一个锁记录的结构,内部可以存储锁定对象的
    Mark Word

    在这里插入图片描述

    让锁记录中 Object reference 指向锁对象,并尝试用 cas 替换 Object 的 Mark Word,将 Mark Word 的值存
    入锁记录

    在这里插入图片描述

    锁记录替换对象头中的MarkWord

    如果 cas 替换成功,对象头中存储了 锁记录地址和状态 00 ,表示由该线程给对象加锁,这时图示如下

    在这里插入图片描述

    如果 cas 失败,有两种情况

    • 如果是其它线程已经持有了该 Object 的轻量级锁,这时表明有竞争,进入锁膨胀过程
    • 如果是自己执行了 synchronized 锁重入,那么再添加一条 Lock Record 作为重入的计数

    在这里插入图片描述

    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 代码块(解锁时)如果有取值为 null 的锁记录,表示有重入,这时重置锁记录,表示重
    入计数减一

    在这里插入图片描述

    当退出 synchronized 代码块(解锁时)锁记录的值不为 null,这时使用 cas 将 Mark Word 的值恢复给对象头

    • 成功,则解锁成功
    • 失败,说明轻量级锁进行了锁膨胀或已经升级为重量级锁,进入重量级锁解锁流程

    锁膨胀

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

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

    当 Thread-1 进行轻量级加锁时,Thread-0 已经对该对象加了轻量级锁

    在这里插入图片描述

    这时 Thread-1 加轻量级锁失败,进入锁膨胀流程

    • 即为 Object 对象申请 Monitor 锁,让 Object 指向重量级锁地址
    • 然后自己进入 Monitor 的 EntryList BLOCKED

    在这里插入图片描述

    当 Thread-0 退出同步块解锁时,使用 cas 将 Mark Word 的值恢复给对象头,失败。这时会进入重量级解锁
    流程,即按照 Monitor 地址找到 Monitor 对象,设置 Owner 为 null,唤醒 EntryList 中 BLOCKED 线程

    自旋优化

    重量级锁竞争的时候,还可以使用自旋(不阻塞,多进行几次循环)来进行优化,如果当前线程自旋成功(即这时候持锁线程已经退出了同步块,释放了锁),这时当前线程就可以避免阻塞(因为阻塞,线程会发生一次上下文切换,极大的浪费性能)。

    自旋重试成功的情况

    在这里插入图片描述

    自旋重试失败的情况

    在这里插入图片描述

    自旋会占用 CPU 时间,单核 CPU 自旋就是浪费,多核 CPU 自旋才能发挥优势。

    在 Java 6 之后自旋锁是自适应的,比如对象刚刚的一次自旋操作成功过,那么认为这次自旋成功的可能性会
    高,就多自旋几次;反之,就少自旋甚至不自旋,总之,比较智能。

    Java 7 之后不能控制是否开启自旋功能

    偏向锁

    轻量级锁在没有竞争时(就自己这个线程),每次重入仍然需要执行 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
     }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    在这里插入图片描述

    在这里插入图片描述

    偏向状态

    偏向锁 使用情况,冲突很少的时候,就一个线程

    如果使用场景是多线程,经常竞争,那么偏向锁就不合适了

    回忆一下对象头格式

    在这里插入图片描述

    一个对象创建时:

    • 如果开启了偏向锁(默认开启),那么对象创建后,markword 值为 0x05 即最后 3 位为 101,这时它的
      thread、epoch、age 都为 0
    • 偏向锁是默认是延迟的,不会在程序启动时立即生效,如果想避免延迟,可以加 VM 参数 -
      XX:BiasedLockingStartupDelay=0 来禁用延迟
    class Dog {}
    
    // 添加虚拟机参数 -XX:BiasedLockingStartupDelay=0 
    public static void main(String[] args) throws IOException {
     Dog d = new Dog();
     ClassLayout classLayout = ClassLayout.parseInstance(d);
     new Thread(() -> {
     log.debug("synchronized 前");
     System.out.println(classLayout.toPrintableSimple(true));
     synchronized (d) {
     log.debug("synchronized 中");
     System.out.println(classLayout.toPrintableSimple(true));
     }
     log.debug("synchronized 后");
     System.out.println(classLayout.toPrintableSimple(true));
     }, "t1").start();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    输出:

    11:08:58.117 c.TestBiased [t1] - synchronized 前
    00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000101 
    11:08:58.121 c.TestBiased [t1] - synchronized 中
    00000000 00000000 00000000 00000000 00011111 11101011 11010000 00000101 
    11:08:58.121 c.TestBiased [t1] - synchronized 后
    00000000 00000000 00000000 00000000 00011111 11101011 11010000 00000101 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    可以从输出中看到,主线程切换了,以后对应的dog对象就给主线程用了。

    注意:处于偏向锁的对象解锁后,线程 id 仍存储于对象头中

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

    在上面测试代码运行时在添加 VM 参数 -XX:-UseBiasedLocking 禁用偏向锁

    输出:

    11:13:10.018 c.TestBiased [t1] - synchronized 前
    00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001 
    11:13:10.021 c.TestBiased [t1] - synchronized 中
    00000000 00000000 00000000 00000000 00100000 00010100 11110011 10001000 
    11:13:10.021 c.TestBiased [t1] - synchronized 后
    00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    可以看到,没有偏向锁了,只有轻量级锁了。(第四行的最后两位00就是轻量锁的标志)

    撤销 - 调用对象 hashCode

    调用了对象的 hashCode,但偏向锁的对象 MarkWord 中存储的是线程 id,如果调用 hashCode 会导致偏向锁被
    撤销,如图所示:

    在这里插入图片描述

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

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

    输出

    11:22:10.386 c.TestBiased [main] - 调用 hashCode:1778535015 
    11:22:10.391 c.TestBiased [t1] - synchronized 前
    00000000 00000000 00000000 01101010 00000010 01001010 01100111 00000001 
    11:22:10.393 c.TestBiased [t1] - synchronized 中
    00000000 00000000 00000000 00000000 00100000 11000011 11110011 01101000 
    11:22:10.393 c.TestBiased [t1] - synchronized 后
    00000000 00000000 00000000 01101010 00000010 01001010 01100111 00000001
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    可以看到,调用之后只能走 轻量级锁了。

    撤销 - 其它线程使用对象

    当有其它线程使用偏向锁对象时,会将偏向锁升级为轻量级锁

    Dog d = new Dog();
     Thread t1 = new Thread(() -> {
     synchronized (d) {
     log.debug(ClassLayout.parseInstance(d).toPrintableSimple(true));
     }
     synchronized (TestBiased.class) {
     TestBiased.class.notify();
     }
     // 如果不用 wait/notify 使用 join 必须打开下面的注释
     // 因为:t1 线程不能结束,否则底层线程可能被 jvm 重用作为 t2 线程,底层线程 id 是一样的
     /*try {
     System.in.read();
     } catch (IOException e) {
     e.printStackTrace();
     }*/
     }, "t1");
     t1.start();
    Thread t2 = new Thread(() -> {
     synchronized (TestBiased.class) {
     try {
     TestBiased.class.wait();
     } catch (InterruptedException e) {
     e.printStackTrace();
     }
     }
     log.debug(ClassLayout.parseInstance(d).toPrintableSimple(true));
     synchronized (d) {
     log.debug(ClassLayout.parseInstance(d).toPrintableSimple(true));
     }
     log.debug(ClassLayout.parseInstance(d).toPrintableSimple(true));
     }, "t2");
     t2.start();
    }
    
    • 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

    输出

    [t1] - 00000000 00000000 00000000 00000000 00011111 01000001 00010000 00000101 
    [t2] - 00000000 00000000 00000000 00000000 00011111 01000001 00010000 00000101 
    [t2] - 00000000 00000000 00000000 00000000 00011111 10110101 11110000 01000000 
    [t2] - 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001 
    
    • 1
    • 2
    • 3
    • 4
    撤销 - 调用 wait/notify

    前面学习了 重量级锁,轻量级锁,偏向锁的概念,纵观这三个概念,发现 wait/notify 只有重量锁的概念中涉及了。

    Dog d = new Dog();
     Thread t1 = new Thread(() -> {
     log.debug(ClassLayout.parseInstance(d).toPrintableSimple(true));
     synchronized (d) {
     log.debug(ClassLayout.parseInstance(d).toPrintableSimple(true));
     try {
     d.wait();
     } catch (InterruptedException e) {
     e.printStackTrace();
     }
     log.debug(ClassLayout.parseInstance(d).toPrintableSimple(true));
     }
     }, "t1");
     t1.start();
     new Thread(() -> {
     try {
     Thread.sleep(6000);
     } catch (InterruptedException e) {
     e.printStackTrace();
     }
     synchronized (d) {
     log.debug("notify");
     d.notify();
     }
     }, "t2").start();
    
    • 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

    输出:

    [t1] - 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000101 
    [t1] - 00000000 00000000 00000000 00000000 00011111 10110011 11111000 00000101 
    [t2] - notify 
    [t1] - 00000000 00000000 00000000 00000000 00011100 11010100 00001101 11001010 
    
    • 1
    • 2
    • 3
    • 4

    可以看到,t1最终以10结尾。

    批量重偏向

    如果对象虽然被多个线程访问,但没有竞争,这时偏向了线程 T1 的对象仍有机会重新偏向 T2,重偏向会重置对象的 Thread ID

    当**撤销偏向锁(对性能也是有一定损耗的)**阈值超过 20 次后,jvm 会这样觉得,我是不是偏向错了呢,于是会在给这些对象加锁时重新偏向至加锁线程

    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).toPrintableSimple(true));
     }
     }
     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).toPrintableSimple(true));
     synchronized (d) {
     log.debug(i + "\t" + ClassLayout.parseInstance(d).toPrintableSimple(true));
     }
     log.debug(i + "\t" + ClassLayout.parseInstance(d).toPrintableSimple(true));
     }
     }, "t2");
     t2.start();
    
    • 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

    输出:

    [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

    [t1] - 19 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101

    [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

    因为阈值是20次,以第20次作比较发现,其偏向的线程确实改变了

    批量撤销

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

    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).toPrintableSimple(true));
     }
     }
     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).toPrintableSimple(true));
     synchronized (d) {
     log.debug(i + "\t" + ClassLayout.parseInstance(d).toPrintableSimple(true));
     }
     log.debug(i + "\t" + ClassLayout.parseInstance(d).toPrintableSimple(true));
     }
     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).toPrintableSimple(true));
     synchronized (d) {
     log.debug(i + "\t" + ClassLayout.parseInstance(d).toPrintableSimple(true));
     }
     log.debug(i + "\t" + ClassLayout.parseInstance(d).toPrintableSimple(true));
     }
     }, "t3");
     t3.start();
     t3.join();
     log.debug(ClassLayout.parseInstance(new Dog()).toPrintableSimple(true));
    
    • 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

    首先先执行t1,偏向都是t1

    然后执行t2, 前十九次一开始先是偏向,然后变成轻量级锁,最后变为不可偏向(偏向撤销),后二十次又变成了偏向锁t2

    然后执行t3,由于 其它线程使用了对象锁,所以偏向状态升级为了轻量级锁,从一开始就是不可偏向的,前19次都是这样的,从第20次开始执行的是撤销操作(相当于总共到了四十次)所以所有的对象都变成了不可偏向的状态

    锁消失

    public void a() throws Exception {
     x++;
    }
     
    public void b() throws Exception {
     Object o = new Object();
     synchronized (o) {
     x++;
     }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    因为有JIT编译器,会对Java字节码做进一步优化

    所以当加锁会影响性能的时候,会自动在编译的过程中消除锁

    相关推荐博客

    👉👉👉 剑指JUC原理-1.进程与线程-CSDN博客

    👉👉👉 剑指JUC原理-2.线程-CSDN博客

    👉👉👉 剑指JUC原理-3.线程常用方法及状态-CSDN博客

    👉👉👉 剑指JUC原理-4.共享资源和线程安全性-CSDN博客

    👉👉👉 剑指JUC原理-6.wait notify-CSDN博客

    👉👉👉 剑指JUC原理-7.线程状态与ReentrantLock-CSDN博客

    👉👉👉 剑指JUC原理-8.Java内存模型-CSDN博客

    👉👉👉 剑指JUC原理-9.Java无锁模型-CSDN博客

    👉👉👉 剑指JUC原理-10.并发编程大师的原子累加器底层优化原理(与人类的优秀灵魂对话)-CSDN博客

    👉👉👉 剑指JUC原理-11.不可变设计-CSDN博客

    👉👉👉 剑指JUC原理-12.手写简易版线程池思路-CSDN博客

    👉👉👉 剑指JUC原理-13.线程池-CSDN博客

    👉👉👉 剑指JUC原理-14.ReentrantLock原理-CSDN博客

    👉👉👉 剑指JUC原理-15.ThreadLocal-CSDN博客

    👉👉👉 剑指JUC原理-16.读写锁-CSDN博客

    👉👉👉 剑指JUC原理-17.CompletableFuture-CSDN博客

    👉👉👉 剑指JUC原理-18.同步协作-CSDN博客

    👉👉👉 剑指JUC原理-19.线程安全集合-CSDN博客

    👉👉👉 剑指JUC原理-20.并发编程实践-CSDN博客

  • 相关阅读:
    博客摘录「 dubbo默认超时设置容易引发的问题(默认1秒)」2023年9月23日
    【HarmonyOS】解决API6 WebView跳转外部浏览器问题、本地模拟器启动黑屏
    涨1100w播放,150w粉!B站UP主仅入站百天竟成功出圈!
    java基础 --泛型
    Android自动化测试,5个必备的测试框架
    Pandas基础入门知识点总结
    力扣-167号题.两数之和II-输入有序的数组
    HTTPS_SSL加密(HTTP终)
    超实用!win10网页录屏的3种方法
    如何将canvas生成的图片转为文件
  • 原文地址:https://blog.csdn.net/qq_40851232/article/details/134094458