精华内容
下载资源
问答
  • 偏向锁验证
    2021-01-28 21:39:05


    相关文章:
    锁状态位之无锁
    锁状态位之偏向锁
    锁状态位之轻量级锁
    锁状态位之重量级锁

    第一次验证

    在这里插入图片描述
    我们创建一个对象,没有上锁,然后再上锁,来观察锁标识位:

    由于只有一个线程加锁,没有产生,所以预期是加偏向锁

    public class Test {
    
        public static void main(String[] args) throws InterruptedException {
            //TimeUnit.SECONDS.sleep(5);  代码很重要,先注释掉,后面需要放开
    
            Object o = new Object();
            System.out.println(ClassLayout.parseInstance(o).toPrintable());  //第一次打印,此时无锁
    
            synchronized (o){
                System.out.println(ClassLayout.parseInstance(o).toPrintable());  //第二次打印,此时有锁
            }
        }
    }
    

    执行结果:

    
    java.lang.Object object internals:
     OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
          0     4        (object header)                           01 00 00 00 (00000001 00000000 00000000 00000000) (1)
          4     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
          8     4        (object header)                           e5 01 f3 27 (11100101 00000001 11110011 00100111) (670237157)
         12     4        (loss due to the next object alignment)
    Instance size: 16 bytes
    Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
    
    java.lang.Object object internals:
     OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
          0     4        (object header)                           88 f8 98 02 (10001000 11111000 10011000 00000010) (43579528)
          4     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
          8     4        (object header)                           e5 01 f3 27 (11100101 00000001 11110011 00100111) (670237157)
         12     4        (loss due to the next object alignment)
    Instance size: 16 bytes
    Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
    
    
    Process finished with exit code 0
    
    

    分析:

    第一条是001无锁的,和锁状态位之无锁中第一章节内容相同,主要看第二条打印:
    在这里插入图片描述

    注意字节是反序的:88 f8 98 02 (10001000第四个字节 11111000第三个字节 10011000第二个字节 00000010第一个字节)

    我们发现第二条打印结果是轻量级锁,不是预期的偏向锁

    那么为什么呢?
    虽然默认开启偏向锁,但JVM会延迟去启动偏向锁,延迟大约三四秒,此时对于系统的前几秒来说,等价于没有开启偏向锁,解决方案有2种:

    • 加sleep 5s再试,放开代码中的语句块
      TimeUnit.SECONDS.sleep(5);
      
    • 关闭延迟开启偏向锁
      -XX:BiasedLockingStartupDelay=0

    对于第二条语句来说,等价于 无锁 ->加轻量级锁(单线程加锁,低竞争度)

    第二次验证

    加了延迟后,对于第二条语句来说,等价于 无锁 ->延迟3后启动偏向锁(偏向线程ID为空) ->偏向锁,设置偏向线程ID为当前线程id

    
    java.lang.Object object internals:
     OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
          0     4        (object header)                           05 00 00 00 (00000101 00000000 00000000 00000000) (5)
          4     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
          8     4        (object header)                           e5 21 f3 27 (11100101 00100001 11110011 00100111) (670245349)
         12     4        (loss due to the next object alignment)
    Instance size: 16 bytes
    Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
    
    java.lang.Object object internals:
     OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
          0     4        (object header)                           05 a8 5c 02 (00000101 10101000 01011100 00000010) (39626757)
          4     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
          8     4        (object header)                           e5 21 f3 27 (11100101 00100001 11110011 00100111) (670245349)
         12     4        (loss due to the next object alignment)
    Instance size: 16 bytes
    Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
    
    
    Process finished with exit code 0
    
    

    第一条显示偏向锁,但是偏向线程ID为空

    jdk1.8时,默认开启偏向锁,创建一个对象的话(在真正开启偏向锁之后才创建的对象), 对象会是可偏向状态,即锁的标识显示是偏向锁,但是偏向线程ID为空,等到后续真的发生需要加偏向锁时,会仅修改偏向线程ID即可。

    第二条显示偏向锁,偏向线程ID为当前线程id

    总结

    对于单线程,低竞争度的锁来说,不是一定加轻量级锁,而是偏向锁的+线程id 来过度的,当然,需要jvm一定时间去开启,需要等待几秒钟

    参考:
    《禁止延迟开启偏向锁jvm参数》

    更多相关内容
  • 偏向锁未启动时,markword的最后3个bit是001 匿名偏向时,markword的最后3个bit是101 jdk8默认的偏向锁启动延迟是4s(BiasedLockingStartupDelay=4000) PS C:\Java_Study\wsc_shell> java -XX:+PrintFlagsFinal ...

    理论知识

    看了一篇博客(https://blog.csdn.net/weixin_45007916/article/details/107535746),其中有以下的图片:
    偏向锁未启动时,markword的最后3个bit是001
    匿名偏向时,markword的最后3个bit是101
    jdk8默认的偏向锁启动延迟是4s(BiasedLockingStartupDelay=4000)

    PS C:\Java_Study\wsc_shell> java -XX:+PrintFlagsFinal -version|findstr /i bias
    java version "1.8.0_221"
    Java(TM) SE Runtime Environment (build 1.8.0_221-b11)
    Java HotSpot(TM) 64-Bit Server VM (build 25.221-b11, mixed mode)
         intx BiasedLockingBulkRebiasThreshold          = 20                                  {product}
         intx BiasedLockingBulkRevokeThreshold          = 40                                  {product}
         intx BiasedLockingDecayTime                    = 25000                               {product}
         intx BiasedLockingStartupDelay                 = 4000                                {product}
         bool TraceBiasedLocking                        = false                               {product}
         bool UseBiasedLocking                          = true                                {product}
         bool UseOptoBiasInlining                       = true                                {C2 product}
    

    在这里插入图片描述
    在这里插入图片描述

    实验环境

    windows10 x64
    C:\Program Files\Java\jdk1.8.0_221\bin\java.exe

    偏向锁未启动

    对象头的最后1个byte是00000001,最后3个bit是001,
    在这里插入图片描述

    匿名偏向

    延迟4100ms让偏向锁启动之后,再打印对象obj的信息内存结构信息
    对象头的最后1个byte是00000101,最后3个bit是101,但是对象头中并未看到指向当前线程的指针
    在这里插入图片描述

    偏向锁中当前线程指针不为空

    此时的偏向锁指向当前线程的数据(对象头低54个bit)为,00000000 00000000 00000000 00000000 00000011 01010110 011010
    Epoch为00
    hex(int(‘000000000000000000000000000000000000001101010110011010’, 2))
    0xd59a
    0xd59a这个和当前线程怎么联系起来?
    在这里插入图片描述

    计算对象的hashcode会使得偏向锁失效-匿名偏向到无锁

    对象头的最后1个byte是00111000,最后3个bit是001,
    从对象头的第26个bit开始的31bit是hashcode值,即
    0011110 11110111 11111110 10001110

    计算 int(‘0011110111101111111111010001110’, 2)
    519569038
    和输出的obj.hashCode()一致
    在这里插入图片描述

    计算对象的hashcode会使得偏向锁失效-升级为轻量级锁

    对象头的最后1个byte是01011000,最后3个bit是000,
    对象头的前62个bit是00000011 00110000 11110110 010110
    hex(int(‘000000110011000011110110010110’, 2))
    ‘0xcc3d96’
    0xcc3d96和当前线程栈中的Lock Record怎么联系起来?

    在这里插入图片描述

    展开全文
  • 偏向锁的进化和废弃

    2022-01-05 00:14:31
    偏向锁的进化和废弃

    锁的演变

    在 JDK1.5 之前,面对 Java 并发问题, synchronized 是一招鲜的解决方案:
    普通同步方法,锁上当前实例对象
    静态同步方法,锁上当前类 Class 对象
    同步块,锁上括号里面配置的对象
    拿同步块来举例:

    public void test(){
      synchronized (object) {
        i++;
      }
    }
    

    经过javap -v编译后的指令如下:

    在这里插入图片描述
    monitorenter指令是在编译后插入到同步代码块的开始位置;monitorexit是插入到方法结束和异常的位置(实际隐藏了try-finally),每个对象都有一个 monitor 与之关联,当一个线程执行到 monitorenter 指令时,就会获得对象所对应的monitor的所有权,也就获得到了对象的锁。
    当另外一个线程执行到同步块的时候,由于它没有对应monitor的所有权,就会被阻塞,此时控制权只能交给操作系统,也就会从user mode切换到kernel mode, 由操作系统来负责线程间的调度和线程的状态变更, 需要频繁的在这两个模式下切换(上下文转换)。这种有点竞争就找内核的行为很不好,会引起很大的开销,所以大家都叫它重量级锁,自然效率也很低。
    来到 JDK1.6,要怎样优化才能让锁变的轻量级一些?答案就是:
    轻量级锁:CPU CAS
    如果 CPU 通过简单的 CAS 能处理加锁/释放锁,这样就不会有上下文的切换,较重量级锁而言自然就轻了很多。但是当竞争很激烈,CAS 尝试再多也是浪费 CPU,权衡一下,不如升级成重量级锁,阻塞线程排队竞争,也就有了轻量级锁升级成重量级锁的过程
    在这里插入图片描述
    HotSpot 的作者经过研究发现,大多数情况下,锁不仅不存在多线程竞争,而且总是由同一个线程多次获得,同一个线程反复获取锁,如果还按照轻量级锁的方式获取锁(CAS),也是有一定代价的,如何让这个代价更小一些呢?
    偏向锁
    偏向锁实际就是锁对象潜意识「偏心」同一个线程来访问,让锁对象记住线程 ID,当线程再次获取锁时,亮出身份,如果同一个 ID 直接就获取锁就好了,是一种load-and-test的过程,相较 CAS 自然又轻量级了一些
    可是多线程环境,也不可能只是同一个线程一直获取这个锁,其他线程也是要干活的,如果出现多个线程竞争的情况,也就有了偏向锁升级的过程
    在这里插入图片描述
    都是同一个锁对象,却有多种锁状态,其目的显而易见:
    占用的资源越少,程序执行的速度越快
    偏向锁,轻量锁,它俩都不会调用系统互斥量(Mutex Lock),只是为了提升性能,多出的两种锁的状态,这样可以在不同场景下采取最合适的策略,所以可以总结性的说:
    偏向锁:无竞争的情况下,只有一个线程进入临界区,采用偏向锁
    轻量级锁:多个线程可以交替进入临界区,采用轻量级锁
    重量级锁:多线程同时进入临界区,交给操作系统互斥量来处理

    认识 Java 对象头

    按照常规理解,识别线程 ID 需要一组 mapping 映射关系来搞定,如果单独维护这个 mapping 关系又要考虑线程安全的问题。奥卡姆剃刀原理,Java 万物皆是对象,对象皆可用作锁,与其单独维护一个 mapping 关系,不如中心化将锁的信息维护在 Java 对象本身上
    Java 对象头最多由三部分构成:
    MarkWord
    ClassMetadata Address
    Array Length (如果对象是数组才会有这部分)
    其中Markword是保存锁状态的关键,对象锁状态可以从偏向锁升级到轻量级锁,再升级到重量级锁,加上初始的无锁状态,可以理解为有 4 种状态。想在一个对象中表示这么多信息自然就要用位存储,在 64 位操作系统中,是这样存储的(注意颜色标记)。
    在这里插入图片描述

    认识偏向锁

    单纯的看上图,还是显得十分抽象,作为程序员的我们最喜欢用代码说话,贴心的 openjdk 官网提供了可以查看对象内存布局的工具 JOL (java object layout)

    <dependency>
      <groupId>org.openjdk.jol</groupId>
      <artifactId>jol-core</artifactId>
      <version>0.14</version>
    </dependency>
    

    场景1

     public static void main(String[] args) {
      Object o = new Object();
      log.info("未进入同步块,MarkWord 为:");
      log.info(ClassLayout.parseInstance(o).toPrintable());
      synchronized (o){
       log.info(("进入同步块,MarkWord 为:"));
       log.info(ClassLayout.parseInstance(o).toPrintable());
      }
     }
    

    来看输出结果:
    在这里插入图片描述
    上面我们用到的 JOL 版本为0.14, 带领大家快速了解一下位具体值,接下来我们就要用0.16版本查看输出结果,因为这个版本给了我们更友好的说明,同样的代码,来看输出结果:
    在这里插入图片描述
    看到这个结果,你应该是有疑问的,JDK 1.6 之后默认是开启偏向锁的,为什么初始化的代码是无锁状态,进入同步块产生竞争就绕过偏向锁直接变成轻量级锁了呢?

    虽然默认开启了偏向锁,但是开启有延迟,大概 4s。原因是 JVM 内部的代码有很多地方用到了synchronized,如果直接开启偏向,产生竞争就要有锁升级,会带来额外的性能损耗,所以就有了延迟策略

    我们可以通过参数-XX:BiasedLockingStartupDelay=0将延迟改为0,但是不建议这么做。我们可以通过一张图来理解一下目前的情况
    在这里插入图片描述
    场景2
    那我们就代码延迟 5 秒来创建对象,来看看偏向是否生效

     public static void main(String[] args) throws InterruptedException {
      // 睡眠 5s
      Thread.sleep(5000);
      Object o = new Object();
      log.info("未进入同步块,MarkWord 为:");
      log.info(ClassLayout.parseInstance(o).toPrintable());
      synchronized (o){
       log.info(("进入同步块,MarkWord 为:"));
       log.info(ClassLayout.parseInstance(o).toPrintable());
      }
     }
    

    重新查看运行结果:
    在这里插入图片描述
    这样的结果是符合我们预期的,但是结果中的biasable状态,在 MarkWord 表格中并不存在,其实这是一种匿名偏向状态,是对象初始化中,JVM 帮我们做的
    这样当有线程进入同步块:

    可偏向状态:直接就 CAS 替换 ThreadID,如果成功,就可以获取偏向锁了
    不可偏向状态:就会变成轻量级锁

    那问题又来了,现在锁对象有具体偏向的线程,如果新的线程过来执行同步块会偏向新的线程吗?
    场景3

    public static void main(String[] args) throws InterruptedException {
      // 睡眠 5s
      Thread.sleep(5000);
      Object o = new Object();
      log.info("未进入同步块,MarkWord 为:");
      log.info(ClassLayout.parseInstance(o).toPrintable());
      synchronized (o){
       log.info(("进入同步块,MarkWord 为:"));
       log.info(ClassLayout.parseInstance(o).toPrintable());
      }
    
      Thread t2 = new Thread(() -> {
       synchronized (o) {
        log.info("新线程获取锁,MarkWord为:");
        log.info(ClassLayout.parseInstance(o).toPrintable());
       }
      });
    
      t2.start();
      t2.join();
      log.info("主线程再次查看锁对象,MarkWord为:");
      log.info(ClassLayout.parseInstance(o).toPrintable());
    
      synchronized (o){
       log.info(("主线程再次进入同步块,MarkWord 为:"));
       log.info(ClassLayout.parseInstance(o).toPrintable());
      }
     }
    

    来看运行结果,奇怪的事情发生了:
    在这里插入图片描述

    • 标记1: 初始可偏向状态
    • 标记2: 偏向主线程后,主线程退出同步代码块
    • 标记3: 新线程进入同步代码块,升级成了轻量级锁
    • 标记4: 新线程轻量级锁退出同步代码块,主线程查看,变为不可偏向状态
    • 标记5: 由于对象不可偏向,同场景1主线程再次进入同步块,自然就会用轻量级锁
      至此,场景一二三可以总结为一张图:
      在这里插入图片描述

    偏向撤销

    在真正讲解偏向撤销之前,需要和大家明确一个概念——偏向锁撤销和偏向锁释放是两码事

    • 撤销:笼统的说就是多个线程竞争导致不能再使用偏向模式的时候,主要是告知这个锁对象不能再用偏向模式
    • 释放:和你的常规理解一样,对应的就是synchronized 方法的退出或 synchronized 块的结束

    何为偏向撤销?

    从偏向状态撤回原有的状态,也就是将 MarkWord 的第 3 位(是否偏向撤销)的值,从 1 变回 0,
    在获取偏向锁的过程中,发现cas失败也就是存在线程竞争时,直接把被偏向的锁对象升级到被加了轻量级锁的状态

    如果只是一个线程获取锁,再加上「偏心」的机制,是没有理由撤销偏向的,所以偏向的撤销只能发生在有竞争的情况下

    想要撤销偏向锁,还不能对持有偏向锁的线程有影响,所以就要等待持有偏向锁的线程到达一个safepoint 安全点(这里的安全点是 JVM 为了保证在垃圾回收的过程中引用关系不会发生变化设置的一种安全状态,在这个状态上会暂停所有线程工作), 在这个安全点会挂起获得偏向锁的线程

    在这个安全点,线程可能还是处在不同状态的

    • 线程不存活或者活着的线程但退出了同步块,很简单,直接撤销偏向就好了
    • 活着的线程但仍在同步块之内,那就要升级成轻量级锁

    偏向锁是特定场景下提升程序效率的方案,可并不代表程序员写的程序都满足这些特定场景,比如这些场景(在开启偏向锁的前提下):

    • 一个线程创建了大量对象并执行了初始的同步操作,之后在另一个线程中将这些对象作为锁进行之后的操作。这种case下,会导致大量的偏向锁撤销操作
    • 明知有多线程竞争(生产者/消费者队列),还要使用偏向锁,也会导致各种撤销
      很显然,这两种场景肯定会导致偏向撤销的,一个偏向撤销的成本无所谓,大量偏向撤销的成本是不能忽视的。那怎么办?既不想禁用偏向锁,还不想忍受大量撤销偏向增加的成本,这种方案就是设计一个有阶梯的底线

    批量重偏向(bulk rebias)

    这是第一种场景的快速解决方案,以 class 为单位,为每个 class 维护一个偏向锁撤销计数器,每一次该class的对象发生偏向撤销操作时,该计数器+1,当这个值达到重偏向阈值(默认20)时:

    BiasedLockingBulkRebiasThreshold = 20
    

    JVM 就认为该class的偏向锁有问题,因此会进行批量重偏向, 它的实现方式就用到了epoch

    Epoch,如其含义「纪元」一样,就是一个时间戳。每个 class 对象会有一个对应的epoch字段,每个处于偏向锁状态对象的mark word中也有该字段,其初始值为创建该对象时 class 中的epoch的值(此时二者是相等的)。每次发生批量重偏向时,就将该值加1,同时遍历JVM中所有线程的栈

    • 找到该 class 所有正处于加锁状态的偏向锁对象,将其epoch字段改为新值
    • class 中不处于加锁状态的偏向锁对象(没被任何线程持有,但之前是被线程持有过的,这种锁对象的 markword 肯定也是有偏向的),保持epoch字段值不变

    这样下次获得锁时,发现当前对象的epoch值和class的epoch不同,本着今朝不问前朝事的原则(上一个纪元),那就算当前已经偏向了其他线程,也不会执行撤销操作,而是直接通过 CAS 操作将其mark word的线程 ID 改成当前线程 ID,这也算是一定程度的优化,毕竟没升级锁;

    如果epoch都一样,说明没有发生过批量重偏向, 如果markword有线程ID,还有其他锁来竞争,那锁自然是要升级的

    批量重偏向是第一阶梯底线,还有第二阶梯底线

    批量撤销(bulk revoke)

    当达到重偏向阈值后,假设该 class 计数器继续增长,当其达到批量撤销的阈值后(默认40)时

    BiasedLockingBulkRevokeThreshold = 40
    

    JVM就认为该 class 的使用场景存在多线程竞争,会标记该 class 为不可偏向。之后对于该 class 的锁,直接走轻量级锁的逻辑

    这就是第二阶梯底线,但是在第一阶梯到第二阶梯的过渡过程中,也就是在彻底禁用偏向锁之前,还给一次改过自新的机会,那就是另外一个计时器:

    BiasedLockingDecayTime = 25000
    
    • 如果在距离上次批量重偏向发生的 25 秒之内,并且累计撤销计数达到40,就会发生批量撤销(偏向锁彻底 game over)
    • 如果在距离上次批量重偏向发生超过 25 秒之外,那么就会重置在[20, 40)内的计数, 再给次机会

    至此,整个偏向锁的工作流程可以用一张图表示:
    在这里插入图片描述

    HashCode 哪去了

    上面场景一,无锁状态,对象头中没有 hashcode;偏向锁状态,对象头还是没有 hashcode,那我们的 hashcode 哪去了?

    首先要知道,hashcode 不是创建对象就帮我们写到对象头中的,而是要经过第一次调用Object::hashCode()或者System::identityHashCode(Object)才会存储在对象头中的。第一次生成的 hashcode后,该值应该是一直保持不变的,但偏向锁又是来回更改锁对象的 markword,必定会对 hashcode 的生成有影响,那怎么办呢?,我们来用代码验证:
    场景一

     public static void main(String[] args) throws InterruptedException {
      // 睡眠 5s
      Thread.sleep(5000);
    
      Object o = new Object();
      log.info("未生成 hashcode,MarkWord 为:");
      log.info(ClassLayout.parseInstance(o).toPrintable());
    
      o.hashCode();
      log.info("已生成 hashcode,MarkWord 为:");
      log.info(ClassLayout.parseInstance(o).toPrintable());
    
      synchronized (o){
       log.info(("进入同步块,MarkWord 为:"));
       log.info(ClassLayout.parseInstance(o).toPrintable());
      }
     }
    

    来看运行结果
    在这里插入图片描述

    结论就是:即便初始化为可偏向状态的对象,一旦调用Object::hashCode()或者System::identityHashCode(Object),进入同步块就会直接使用轻量级锁

    场景二

    假如已偏向某一个线程,然后生成 hashcode,然后同一个线程又进入同步块,会发生什么呢?来看代码:

     public static void main(String[] args) throws InterruptedException {
      // 睡眠 5s
      Thread.sleep(5000);
    
      Object o = new Object();
      log.info("未生成 hashcode,MarkWord 为:");
      log.info(ClassLayout.parseInstance(o).toPrintable());
    
      synchronized (o){
       log.info(("进入同步块,MarkWord 为:"));
       log.info(ClassLayout.parseInstance(o).toPrintable());
      }
    
      o.hashCode();
      log.info("生成 hashcode");
      synchronized (o){
       log.info(("同一线程再次进入同步块,MarkWord 为:"));
       log.info(ClassLayout.parseInstance(o).toPrintable());
      }
     }
    

    查看运行结果:
    在这里插入图片描述

    结论就是:同场景一,会直接使用轻量级锁

    场景三
    那假如对象处于已偏向状态,在同步块中调用了那两个方法会发生什么呢?继续代码验证:

     public static void main(String[] args) throws InterruptedException {
      // 睡眠 5s
      Thread.sleep(5000);
    
      Object o = new Object();
      log.info("未生成 hashcode,MarkWord 为:");
      log.info(ClassLayout.parseInstance(o).toPrintable());
    
      synchronized (o){
       log.info(("进入同步块,MarkWord 为:"));
       log.info(ClassLayout.parseInstance(o).toPrintable());
       o.hashCode();
       log.info("已偏向状态下,生成 hashcode,MarkWord 为:");
       log.info(ClassLayout.parseInstance(o).toPrintable());
      }
     }
    

    来看运行结果:
    在这里插入图片描述

    结论就是:如果对象处在已偏向状态,生成 hashcode 后,就会直接升级成重量级锁

    最后用书中的一段话来描述 锁和hashcode 之前的关系:
    在这里插入图片描述
    调用 Object.wait() 方法会发生什么?

    Object 除了提供了上述 hashcode 方法,还有wait()方法,这也是我们在同步块中常用的,那这会对锁产生哪些影响呢?来看代码:

     public static void main(String[] args) throws InterruptedException {
      // 睡眠 5s
      Thread.sleep(5000);
    
      Object o = new Object();
      log.info("未生成 hashcode,MarkWord 为:");
      log.info(ClassLayout.parseInstance(o).toPrintable());
    
      synchronized (o) {
       log.info(("进入同步块,MarkWord 为:"));
       log.info(ClassLayout.parseInstance(o).toPrintable());
    
       log.info("wait 2s");
       o.wait(2000);
    
       log.info(("调用 wait 后,MarkWord 为:"));
       log.info(ClassLayout.parseInstance(o).toPrintable());
      }
     }
    

    查看运行结果:
    在这里插入图片描述

    结论就是,wait 方法是互斥量(重量级锁)独有的,一旦调用该方法,就会升级成重量级锁

    最后再继续丰富一下锁对象变化图:在这里插入图片描述

    废弃偏向锁

    为啥要废弃偏向锁,因为维护成本有些高了,来看 Open JDK 官方声明,JEP 374: Deprecate and Disable Biased Locking,相信你看上面的文字说明也深有体会,为了一个现在少有的场景付出了巨大的代码实现
    在这里插入图片描述
    这个说明的更新时间距离现在很近,在 JDK15 版本就已经开始了,一句话解释就是维护成本太高,最终JDK 15 之前,偏向锁默认是 enabled,从 15 开始,默认就是 disabled,除非显示的通过UseBiasedLocking 开启,偏向锁给 JVM 增加了巨大的复杂性,只有少数非常有经验的程序员才能理解整个过程,维护成本很高,大大阻碍了开发新特性的进程。

    轻量级和重量级锁,hashcode 存在了什么位置?
    如果进入各种锁状态,那么会缓存在其他地方,一般是获取锁的线程里面存储,恢复无锁(即释放锁)会改回原有的哈希值。
    轻量级-》lockRecord
    重量级锁-》objectMonitor

    展开全文
  • Synchronized原理(偏向锁篇)

    千次阅读 热门讨论 2021-09-24 17:32:33
    Synchronized原理(偏向锁篇) 传统的锁机制 传统的锁依赖于系统的同步函数,在linux上使用mutex互斥锁,最底层实现依赖于futex,这些同步函数都涉及到用户态和内核态的切换、进程的上下文切换。这种状态转换需要...

    Synchronized原理(偏向锁篇)

    传统的锁机制

    传统的锁依赖于系统的同步函数,在linux上使用mutex互斥锁,最底层实现依赖于futex,这些同步函数都涉及到用户态和内核态的切换、进程的上下文切换。这种状态转换需要耗费处理器时间。如果同步代码块中的内容过于简单,状态转换消耗的时间有可能比用户代码执行的时间还要长,这种依赖于操作系统Mutex Lock所实现的锁我们称之为**“重量级锁”**。

    对于加了synchronized关键字但运行时并没有多线程竞争,或两个线程接近于交替执行的情况,使用传统锁机制无疑效率是会比较低的。

    在JDK 1.6之前,synchronized只有传统的锁机制,因此给开发者留下了synchronized关键字相比于其他同步机制性能不好的印象。

    在JDK 1.6引入了两种新型锁机制:偏向锁和轻量级锁,它们的引入是为了解决在没有多线程竞争或基本没有竞争的场景下因使用传统锁机制带来的性能开销问题。

    目前锁一共有4种状态,级别从低到高依次是:无锁偏向锁轻量级锁重量级锁

    在看这几种锁机制的实现前,我们先来了解下oop-klass模型。

    oop-klass

    简介

    简单地说,一个Java类在JVM中被拆分为了两个部分:数据和描述信息,分别对应OOP和Klass。OOP表示java对象应该承载的数据,而Klass表示描述对象有多大,函数地址,对象大小,静态区域大小。

    在这里插入图片描述

    oop

    Ordinary Object Pointer (普通对象指针),它用来表示对象的实例信息,看起来像个指针实际上是藏在指针里的对象。Klass是在class文件在加载过程中创建的,OOP则是在Java程序运行过程中new对象时创建的。

    一个OOP对象包含以下几个部分:
    在这里插入图片描述

    考虑到虚拟器的储存效率,Mark Word被设计成一个非固定的数据结构以便在极小的空间内存储尽量多的信息,它会根据对象的状态复用自己的存储空间。

    下图表示32位机器中的对象头在不同情况下的结构

    在这里插入图片描述

    对象头的另外一部分是类型指针,即是对象指向它的类的元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例。

    Klass

    Klass包含元数据和方法信息,用来描述Java类。

    Klass主要有两个功能:

    • 实现语言层面的Java类
    • 实现Java对象的分发功能

    一般jvm在加载class文件时,会在方法区创建InstanceKlass,表示其元数据,包括常量池、字段、方法等。

    在这里插入图片描述

    这里着重介绍InstanceKlass的两个字段:

    _prototype_header:原型头,用于用于标识Mark Word原型,在对象被创建出来以后,会从_prototype_header拷贝数据到对象头的Mark Word中。

    revocation_count:撤销计数器,每次该class的对象发生偏向锁撤销操作时,计数器会自增1,当达到批量重偏向阈值(默认20)时,会执行批量重偏向;当达到批量撤销的阈值(默认40)时,会执行批量撤销。

    代码示例

    class Model
    {
        public static int a = 1;
        public int b;
    
        public Model(int b) {
            this.b = b;
        }
    }
    
    public static void main(String[] args) {
        int c = 10;
        Model modelA = new Model(2);
        Model modelB = new Model(3);
    }
    

    上述代码的OOP-Klass模型如下所示

    在这里插入图片描述

    锁升级模型

    复杂版(参考熬丙大佬的图做了些修改)

    在这里插入图片描述

    简易版

    在这里插入图片描述

    偏向锁

    Java是支持多线程的语言,因此在很多二方包、基础库中为了保证代码在多线程的情况下也能正常运行,也就是我们常说的线程安全,都会加入如synchronized这样的同步语义。但是在应用在实际运行时,很可能只有一个线程会调用相关同步方法。比如下面这个demo:

    import java.util.ArrayList;
    import java.util.List;
    
    public class SyncDemo1 {
    
        public static void main(String[] args) {
            SyncDemo1 syncDemo1 = new SyncDemo1();
            for (int i = 0; i < 100; i++) {
                syncDemo1.addString("test:" + i);
            }
        }
    
        private List<String> list = new ArrayList<>();
    
        public synchronized void addString(String s) {
            list.add(s);
        }
    
    }
    

    在这个demo中为了保证对list操纵时线程安全,对addString方法加了synchronized的修饰,但实际使用时却只有一个线程调用到该方法,对于轻量级锁而言,每次调用addString时,加锁解锁都有一个CAS操作;对于重量级锁而言,加锁也会有一个或多个CAS操作(这里的’一个‘、’多个‘数量词只是针对该demo,并不适用于所有场景)。

    在JDK1.6中为了提高一个对象在一段很长的时间内都只被一个线程用做锁对象场景下的性能,引入了偏向锁,在第一次获得锁时,会有一个CAS操作,之后该线程再获取锁,只会执行几个简单的命令,而不是开销相对较大的CAS命令。

    引入偏向锁的目的

    在没有多线程竞争的情况下,尽量减少不必要的轻量级锁的执行。轻量级锁的获取及释放依赖多次CAS原子指令,而偏向锁只依赖一次CAS原子指令。但在多线程竞争时,需要进行偏向锁撤销步骤,因此其撤销的开销必须小于节省下来的CAS开销,否则偏向锁并不能带来收益。JDK 1.6中默认开启偏向锁,可以通过-XX:-UseBiasedLocking来禁用偏向锁。

    优点

    加锁解锁无需额外的消耗,和非同步方法时间相差纳秒级别。

    缺点

    如果竞争的线程多,那么会带来额外的锁撤销的消耗(撤销时会暂停原所有者线程)。

    适用场景

    锁不存在竞争关系,或者说线程总是能有序的获取到锁(线程A执行完同步代码块后线程B才尝试去获取锁)。


    工作流程

    在这里插入图片描述

    对象创建

    当JVM启用了偏向锁模式(1.6以上默认开启),当新创建一个对象的时候,如果该对象所属的class没有关闭偏向锁模式(什么时候会关闭一个class的偏向模式下文会说,默认所有class的偏向模式都是是开启的),那新创建对象的mark word将是可偏向状态,此时mark word中的thread id(参见上文偏向锁状态下的mark word格式)为0,表示未偏向任何线程,也叫做匿名偏向(anonymously biased)。

    需要注意的是,即使模式默认开启,出于性能(启动时间)的原因,在JVM启动后的的头4秒钟这个feature是被禁止的。这也意味着在此期间,_prototype_header会将它的locked_bias位设置为0,以禁止实例化的对象被偏向。4秒钟之后,所有的_prototype_headerlocked_bias位会被重设为1,如此新的对象就可以被偏向锁定了。

    初始状态

    当对象头的locked_bias为0时,此对象处于未锁定不可偏向的状态。

    在此状态下,如果有线程尝试获取此锁,会升级为轻量级锁。如果有多个线程尝试获取此锁,会升级为重量级锁。

    在这里插入图片描述

    注:对象的hashCode并不是一创建就计算好的,而是在调用hasCode方法后,储存在对象头中的。且一旦被偏向的对象进行hashcode计算时,不管该对象有没有被锁定,都会触发偏向锁撤销。

    此状态出现的可能:

    • 计算hashcode
    • 偏向锁被禁用
    • 偏向锁被撤销(什么情况下撤销下文会说)

    当对象头的locked_bias为1时,此对象处于以下三种状态:

    • 匿名偏向(Anonymously biased)
      在此状态下Thread Id为NULL(0),意味着还没有线程偏向于这个锁对象。第一个试图获取该锁的线程将会面临这个情况,使用原子CAS指令可将该锁对象绑定于当前线程。这是允许偏向锁的类对象的初始状态。

      在这里插入图片描述

    • 可重偏向(Rebiasable)
      在此状态下,偏向锁的epoch字段是无效的(与锁对象对应InstanceKlass_prototype_headerepoch值不匹配)。下一个试图获取锁对象的线程将会面临这个情况,使用原子CAS指令可将该锁对象绑定于当前线程。在批量重偏向的操作中,未被持有的锁对象都被至于这个状态,以便允许被快速重偏向。

      在这里插入图片描述

    • 已偏向(Biased)
      在此状态下,Thread Id非空,且epoch为有效值——意味着其他线程正在使用这个锁对象。

      在这里插入图片描述

    加锁过程

    偏向锁获取可以分为4个步骤:

    1. 验证对象Mark Wordlocked_bias

      如果是0,则该对象不可偏向,走轻量级锁逻辑;如果是1,继续下一步操作。

    2. 验证对象所属InstanceKlass_prototype_headerlocked_bias

      确认_prototype_headerlocked_bias位是否为0,如果是0,则该类所有对象全部不允许被偏向锁定,并且该类所有对象的locked_bias位都需要被重置,使用轻量级锁替换;如果是1,继续下一步操作。

    3. 比对对象和原型的epoch

      校验对象的Mark Wordepoch位是否与该对象所属InstanceKlass_prototype_headerepoch匹配。如果不匹配,则表明偏向已过期,继续下一步操作,尝试重入锁或者重偏向;如果匹配,继续下一步操作,尝试重入锁或升级为轻量级锁定。

    4. 校检onwer线程

      比较偏向线程ID与当前线程ID。如果匹配,则表明当前线程已经获得了偏向,可以安全返回。如果不匹配,对象锁被假定为匿名偏向状态,当前线程应该尝试使用CAS指令获得偏向。如果失败的话,就尝试撤销(很可能引入安全点),然后回退到轻量级锁;如果成功,当前线程成功获得偏向,可直接返回。

      这里提个问题,为什么在偏向线程ID与当前线程ID不匹配的情况下还需要尝试使用CAS指令获取偏向呢?

      因为上一步存在对象头和原型头的epoch位不相等的情况,即允许重偏向。假如这次CAS成功,则此对象锁可以重新偏向于获取锁的线程;如果失败,则代表获取的时候产生了竞争,需要升级为轻量级锁定或重量级锁定。

      偏向锁获取图

      在这里插入图片描述

      我们来分解一下这个图

      case 1:当该对象第一次被线程获得锁的时候,发现是匿名偏向状态,则会用CAS指令,将mark word中的thread id由0改成当前线程Id。如果成功,则代表获得了偏向锁,继续执行同步块中的代码。否则,将偏向锁撤销,升级为轻量级锁。

      case 2:当被偏向的线程再次进入同步块时,发现锁对象偏向的就是当前线程,在通过一些额外的检查后,会往当前线程的栈中添加一条Displaced Mark Word为空的Lock Record中,然后继续执行同步块的代码,因为操纵的是线程私有的栈,因此不需要用到CAS指令;由此可见偏向锁模式下,当被偏向的线程再次尝试获得锁时,仅仅进行几个简单的操作就可以了,在这种情况下,synchronized关键字带来的性能开销基本可以忽略。

      case 3:当其他线程进入同步块时,发现已经有偏向的线程了,则会进入到撤销偏向锁的逻辑里,一般来说,会在safepoint中去查看偏向的线程是否还存活,如果存活且还在同步块中则将锁升级为轻量级锁,原偏向的线程继续拥有锁,当前线程则走入到锁升级的逻辑里;如果偏向的线程已经不存活或者不在同步块中,则将对象头的mark word改为无锁状态(unlocked),之后再升级为轻量级锁。

      由此可见,偏向锁升级的时机为:当锁已经发生偏向后,只要有另一个线程尝试获得偏向锁,则该偏向锁就会升级成轻量级锁。当然这个说法不绝对,因为还有批量重偏向这一机制(下文会讲到)。

    解锁过程

    当偏向锁被一个线程获取到时,会往所有者线程的栈中添加一条Displaced Mark Word为空的Lock Record

    在这里插入图片描述

    当有其他线程尝试获得锁时,根据遍历偏向线程的lock record来确定该线程是否还在执行同步块中的代码。因此偏向锁的解锁很简单,仅仅将栈中的最近一条lock recordobj字段设置为null。需要注意的是,偏向锁的解锁步骤中并不会修改对象头中的thread id。

    在这里插入图片描述

    锁撤销

    一般来说,以下三种情况会触发锁撤销:

    • 被偏向的对象进行hashcode计算时,不管该对象有没有被锁定,都会触发偏向锁撤销,通过CAS将计算好的hashcode存入Mark Word中。

    • 当前的对象是已偏向未锁定状态,即所有者线程已经退出同步代码块,此时有其它的线程尝试获取偏向锁;在允许重偏向的情况下,原所有者线程会触发解锁,将对象恢复成匿名可偏向的状态;如果不允许重偏向,则会触发偏向锁撤销,将对象设置为未锁定且不可偏向的状态,竞争者线程按轻量级锁的逻辑去获取锁。

    • 当前的对象是已偏向已锁定的状态,即所有者线程正在执行同步代码块,此时有其它的线程尝试获取偏向锁,由于所有者线程仍需要持有这把锁,此时产生了锁竞争,偏向锁不适合处理这种有竞争的场景,即会触发偏向锁撤销,原偏向锁持有者线程会升级为轻量级锁定状态,竞争者线程按轻量级锁的逻辑去获取锁。

    总结来说就是

    1. 计算hashcode。
    2. 锁定状态到禁用状态。
    3. 锁升级。

    需要注意的是,锁撤销和解锁是两个不同的概念。撤销是指在获取偏向锁的过程因为不满足条件导致要将锁对象改为非偏向锁状态,即低三位变为010;解锁是指退出同步块的过程,即移除最近的锁记录。

    批量重偏向与批量撤销

    思考

    上文我们说到当持有偏向锁的线程已经执行完同步代码块,此时其它线程尝试获取偏向锁会触发原所有者线程的解锁操作,那解锁成功后是不是意味着此偏向锁可以被其它线程锁获取呢?

    大部分情况下,当偏向锁已经偏向于一个线程时,即使所有者线程不再占用此锁,也很难偏向于新的对象。如果有其它的线程试图获取此偏向锁,则会撤销偏向锁,进入锁升级的流程。需要注意的是,在执行撤销操作的时候,会等待线程进入safe point,然后暂停线程。当该class衍生出的多个对象都执行偏向锁撤销的话,也是一笔不小的性能开销。

    成因

    从偏向锁的加锁解锁过程中可看出,当只有一个线程反复进入同步块时,偏向锁带来的性能开销基本可以忽略,但是当有其他线程尝试获得锁时,就需要等到safe point时,再将偏向锁撤销为无锁状态或升级为轻量级,会消耗一定的性能,所以在多线程竞争频繁的情况下,偏向锁不仅不能提高性能,还会导致性能下降。
    于是,就有了批量重偏向与批量撤销的机制。

    解决场景
    批量重偏向(bulk rebias)

    避免短时间内大量偏向锁的撤销。例如一个线程创建了大量对象并执行了初始的同步操作,后来另一个线程也来将这些对象作为锁对象进行操作,这样会导致大量的偏向锁撤销操作。当执行批量重偏向后,如果原偏向锁持有者线程不再执行同步块,则锁可以偏向于新线程。

    批量撤销(bulk revoke)

    在明显多线程竞争剧烈的场景下使用偏向锁是不合适的,例如生产生-消费者模式,会有多个线程参与竞争。当执行批量撤销后,会直接把class中的locked_bias字段置0,该class已经是偏向锁模式的实例会批量撤销偏向锁,该class新分配的对象的mark word则是无锁模式。

    原理

    以class为单位,为每个class维护一个偏向锁撤销计数器,每一次该class的对象发生偏向撤销操作时,该计数器+1,当这个值达到重偏向阈值(默认20)时,JVM就认为该class的偏向锁有问题,因此会进行批量重偏向。每个class对象会有一个对应的epoch字段,每个处于偏向锁状态对象的mark word中也有该字段,其初始值为创建该对象时,class中的epoch的值。每次发生批量重偏向时,就将该值+1,同时遍历JVM中所有线程的栈,找到该class所有正处于加锁状态的偏向锁,将其epoch字段改为新值。下次获得锁时,发现当前对象的epoch值和class的epoch不相等,那就算当前已经偏向了其他线程,也不会执行撤销操作,而是直接通过CAS操作将其mark word的Thread Id 改成当前线程Id。

    当达到重偏向阈值后,假设该class计数器继续增长,当其达到批量撤销的阈值后(默认40),JVM就认为该class的使用场景存在多线程竞争,会标记该class为不可偏向,之后,对于该class的锁,直接走轻量级锁的逻辑。

    图解

    在这里插入图片描述


    在这里插入图片描述


    在这里插入图片描述


    在这里插入图片描述


    在这里插入图片描述


    在这里插入图片描述


    在这里插入图片描述


    在这里插入图片描述


    在这里插入图片描述


    在这里插入图片描述


    在这里插入图片描述


    归纳总结

    偏向锁的适用场景

    在一个时间段内每一个时刻都是只有一个线程使用同一个对象,但是不是每一时刻都是同一个线程。

    如何关闭偏向锁和延迟

    偏向锁在Java 6Java 7里是默认启用的,但是它在应用程序启动几秒钟之后才激活,如有必要可以使用JVM参数来关闭延迟:-XX:BiasedLockingStartupDelay=0。如果你确定应用程序里所有的锁通常情况下处于竞争状态,可以通过JVM参数关闭偏向锁:-XX:-UseBiasedLocking=false,那么程序默认会进入轻量级锁状态。

    怎样确定偏向锁有没有被占用

    如果锁对象属于匿名可偏向的状态,即线程ID=0,那么线程可以直接获取到偏向锁;如果锁对象属于已偏向的状态,那么就从记录的线程ID去查找锁记录,如果未找到,则说明此对象锁属于已偏向未锁定状态,此时会先判断是否允许重偏向来决定获取偏向锁还是走锁升级的逻辑;如果找到了锁记录,则说明此对象锁属锁属于已偏向已锁定状态,此时会直接走锁升级逻辑的判断(依旧存在重偏向的可能)。

    什么情况下会允许重偏向,什么情况下不允许重偏向

    当偏向锁处于已偏向未锁定状态状态时,通过比对对象头和原型头的epoch,如果不相等,则代表已经达到批量重偏向的阈值,允许进行重偏向。其它情况下,只要该锁已经偏向于线程,则不允许重偏向。

    重偏向的时候需要先撤销偏向锁吗

    不需要,重偏向仅仅需要通过CAS更新线程的id,如果成功对象锁会重偏向于新的线程,如果失败代表发生了竞争,此时才会撤销偏向锁,走锁升级的逻辑。

    偏向锁怎样支持锁重入的,重入的流程是什么

    当偏向锁重入时,会先去遍历栈中的Lock Record空间,从低位往高位找到第一个可用的Lock Record(即obj指向为空),并将obj字段指向当前锁对象;当偏向锁解锁时,也会遍历栈中Lock Record空间,从低位开始找到第一个和当前锁对象相关的Lock Record移除掉。当未遍历到和此锁对象有关联的Lock Record时,代表原偏向锁持有者线程已经执行完该对象锁定的同步代码。

    End

    Java中的synchronized有偏向锁、轻量级锁、重量级锁三种形式,分别对应了锁只被一个线程持有、不同线程交替持有锁、多线程竞争锁三种情况。当条件不满足时,锁会按偏向锁->轻量级锁->重量级锁 的顺序升级。JVM种的锁也是能降级的,只不过条件很苛刻,不在我们讨论范围之内。该篇文章主要是对Java的偏向锁做介绍,下一篇文章会继续分析轻量级锁。

    参考文献

    死磕Synchronized底层实现–偏向锁 · Issue #13 · farmerjohngit/myblog (github.com)

    不可不说的Java“锁”事 - 美团技术团队 (meituan.com)

    展开全文
  • 文章目录Java锁Synchronized关键字学习系列之偏向锁synchronized 锁升级偏向锁匿名偏向偏向锁的加锁和释放锁偏向锁流程图总结学习参考源代码 Java锁Synchronized关键字学习系列之偏向锁 synchronized 锁升级 在多...
  • Synchronized之偏向锁

    2022-02-28 11:06:08
    本次讨论基于jdk1.8。...偏向锁->轻量级锁->独占锁(重量级锁)。但在实际模拟的时候发现synchronized直接从无锁状态变为了轻量级锁。接下来是实验过程,在此之前先了解下各种锁在对象头中的表现 ...
  • 普通同步方法,上当前实例对象 静态同步方法,上当前类 Class 对象 同步块,上括号里面配置的对象 拿同步块来举例: publicvoidtest(){ synchronized(object){ i++; } } 经过javap -v编译后的...
  • 我们都知道无锁状态是对象头是有位置存储hashcode的,而变为偏向锁状态是没有位置存储hashcode的,今天我们来通过实现验证这个问题:当锁状态为偏向锁的时候,hashcode存到哪里去了? 先说结论: jdk8偏向锁是默认...
  • java锁结构之无锁、偏向锁、轻量级锁、重量级锁
  • java 偏向锁

    千次阅读 2019-07-23 22:30:52
    文章目录偏向锁1、偏向锁是什么2、优缺点优点缺点3、偏向锁怎么获取前提4、偏向锁的撤销5、可重偏向状态(Rebiasable)6、BiasedLockingBulkRebiasThreshold 参数是干什么用的7、到BiasedLockingBulkRevokeThreshold ...
  • 普通同步方法,上当前实例对象 静态同步方法,上当前类 Class 对象 同步块,上括号里面配置的对象 拿同步块来举例: public void test(){ synchronized (object) { i++; } } 复制代码 经过javap -v编译...
  • 1.三种的简介 内置是JVM提供的最便捷的线程同步工具,在代码块或方法声明上添加synchronized关键字即可使用内置。对其优化使的juc包中的各种类性能提高。 重量级:内置在Java中被抽象为监视器(monitor...
  • 普通同步方法,上当前实例对象 静态同步方法,上当前类 Class 对象 同步块,上括号里面配置的对象 拿同步块来举例: public void test(){ synchronized (object) { i++; } } 复制代码 经过 javap -v ...
  • 背景在 JDK1.5 之前,面对 Java 并发问题, synchronized 是一招鲜的解决方案:普通同步方法,上当前实例对象静态同步方法,上当前类 Class 对象同步块,上括...
  • 偏向锁

    千次阅读 2019-04-12 01:42:15
    注:本文翻译自...HotSpot支持存储释放偏向锁,以及偏向锁的批量重偏向和撤销。这个特性可以通过JVM的参数进行切换,而且这是默认支持的。Unlock状态下MarkWord的一个比特位用于标识该对象偏向锁是否被使用...
  • Java中的偏向锁,轻量级锁, 重量级锁解析

    万次阅读 多人点赞 2018-08-13 18:39:49
    参考文章 聊聊并发(二)Java SE1.6中的Synchronized Lock Lock Lock: Enter! 5 Things You Didn’t Know About Synchronization in Java and Scala ...Java 中的 在 Java 中主要2种加锁机制: synchr...
  • 想要撤销偏向锁,还不能对持有偏向锁的线程有影响,所以就要等待持有偏向锁的线程到达一个 safepoint 安全点 (这里的安全点是 JVM 为了保证在垃圾回收的过程中引用关系不会发生变化设置的一种安全状态,在这个...
  • 轻量级锁、偏向锁、重量级锁详情

    千次阅读 热门讨论 2018-11-17 13:48:47
    这篇文章是上篇文章是否真的理解了偏向锁、轻量级锁、重量级锁(锁膨胀)、自旋锁、锁消除、锁粗化,知道重偏向吗?的补充,对于偏向锁,网上有些对于它的原理解读过于简单,简单得似乎是错误的,最显眼的是对于Mark...
  • 文章目录Java锁synchronized关键字学习系列之偏向锁升级无锁偏向锁原理批量重偏向和批量撤销偏向锁升级偏向锁升级轻量级锁偏向锁升级重量级锁参考源代码 Java锁synchronized关键字学习系列之偏向锁升级 前面几篇博文...
  • 一文带你了解synchronized的各种,这些是如何变化的,什么样的操作会导致发生变化?
  • Mark Word:存储对象的hashcode、分代年龄、信息等 Class Metadata Address(类型指针):存储到对象类型数据的指针 Array length:数组长度(数组特有) 实例数据 对象真正存储的有效信息,继承自父类及子类所...
  • 在介绍之前我们先介绍一个线程不安全的例子,一个全局的list,开2个线程往里面插入数据,代码如下:package com.jvm.day6.lock.demo;import java.util.ArrayList;import java.util.List;/*** 测试都线程共享一个...
  • 偏向锁的理解

    千次阅读 2019-05-04 19:28:03
    偏向锁: 是为了在资源没有被其他线程竞争的情况下尽量减少锁带来的性能开销。在锁对象的对象头中有一个ThreadId字段,当第一个线程访问锁时,如果该锁没有被其他线程访问,即ThreadId字段为空,那么JVM让其持有...
  • MarkWord记录对象的状态、分代年龄等对象信息。以下为64bitJVM的对象头MarkWord信息: 具体关于对象内存布局信息的知识,可以参见:Java对象的内存布局 本文主要介绍如何使用JOL去观察 二、使用Maven导入JOL相关...
  • 1 前言 在Java SE1.6之前,synchronized一直都是重量级锁,如果某个...在Java SE1.6之后,为了减少获得锁和释放锁带来的性能消耗而引入的偏向锁和轻量级锁,以及锁的存储结构和升级过程。这些锁就存在锁对象的对象...
  • 禁止延迟开启偏向锁jvm参数

    千次阅读 热门讨论 2020-03-14 20:51:13
    jdk6默认开启偏向锁,但是是输入延时开启,也就是说,程序刚启动创建的对象是不会开启偏向锁的,几秒后后创建的对象才会开启偏向锁的 -XX:BiasedLockingStartupDelay=0 验证 引入依赖包 <!--可以查看mark word...
  • synchronized前言JDK对Synchronize的优化-锁膨胀偏向锁轻量级锁重量级锁重量级锁模式下的SynchronizeSynchronize 的核心组件Synchronized 的流程Synchronized 的实现Synchronized 的作用范围synchronized的简单使用 ...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 7,336
精华内容 2,934
热门标签
关键字:

偏向锁验证