美文网首页
Synchronized 锁升级过程

Synchronized 锁升级过程

作者: Miss_Ella | 来源:发表于2021-01-16 19:01 被阅读0次

对象结构

Java 对象存储在堆中,那么一个java对象在内存中的结构哪些信息呢?包含对象头,对象体和对齐字节,以下是一个java对象在32位操作系统中的结构


image.png

对象头

  • MarkWord, 记录锁信息(synchronized,hashcode, 最原始的hashcode, 自己写的不算, gc信息, 4bit, 分代年龄,就是gc的信息, 最大15。
  • 对象指针,指向对象的指针,其指向的位置是对象对应的Class对象(其对应的元数据对象)的内存地址。如果是数组就是长度。

对象体

  • 对象数据, 实际的对象数据,其大小由各个成员变量的大小决定,比如:byte和boolean是1个字节,short和char是2个字节,int和float是4个字节,long和double是8个字节,reference是4个字节。

对齐字节

  • 如果占用内存不能被8整除,内存加到可以被8整除为止

Synchronized 锁优化

锁状态有四种,其中偏向锁和轻量级锁都是在用户态,而重量级锁需要向操作系统申请,在内核态。

  • 无锁
  • 偏向锁
  • 轻量级锁
  • 重量级锁

偏向锁

为什么要引入偏向锁

  • 因为经过HotSpot的作者大量的研究发现,大多数(90%)时候是不存在锁竞争的,常常是一个线程多次获得同一个锁,因此如果每次都要竞争锁会增大很多没有必要付出的代价,为了降低获取锁的代价,才引入的偏向锁。

如何上偏向锁

  • 上偏向锁是在对象的markword上贴上线程id,一般偏向第一个线程。

偏向锁升级

  • 当线程1访问代码块并获取锁对象时,会在java对象头和栈帧中记录偏向的锁的threadID,因为偏向锁不会主动释放锁,因此以后线程1再次获取锁的时候,需要比较当前线程的threadID和Java对象头中的threadID是否一致,如果一致(还是线程1获取锁对象),则无需使用CAS来加锁、解锁;如果不一致(其他线程,如线程2要竞争锁对象,而偏向锁不会主动释放因此还是存储的线程1的threadID),那么需要查看Java对象头中记录的线程1是否存活,如果没有存活,那么锁对象被重置为无锁状态,其它线程(线程2)可以竞争将其设置为偏向锁;如果存活,那么立刻查找该线程(线程1)的栈帧信息,如果还是需要继续持有这个锁对象,那么暂停当前线程1,撤销偏向锁,升级为轻量级锁,如果线程1 不再使用该锁对象,那么将锁对象状态设为无锁状态,重新偏向新的线程。

如何取消偏向锁

  • 偏向锁是默认开启的,而且开始时间一般是比应用程序启动慢几秒,如果不想有这个延迟,那么可以使用JVM调优: -XX:BiasedLockingStartUpDelay=0;
    如果不想要偏向锁,那么可以通过-XX:-UseBiasedLocking = false来设置;

轻量级锁

为什么要引入轻量级锁

  • 轻量级锁考虑的是竞争锁对象的线程不多,而且线程持有锁的时间也不长的情景。因为阻塞线程需要CPU从用户态转到内核态,代价较大,如果刚刚阻塞不久这个锁就被释放了,那这个代价就有点得不偿失了,因此这个时候就干脆不阻塞这个线程,让它自旋这等待锁释放。

上轻量级锁过程

  • 轻量级锁为CAS自旋锁(自旋锁, compare and swap),在需要上锁的线程上面进行操作。步骤如下:
  1. 将竞争的变量值拷贝本线程的内存
  2. 在本线程计算结果
  3. 比较竞争变量在1中的值和当前值, 如果一致,说明没有其他进程改写(也可能改写了但是值一样),将2中计算的值填入变量中。如果1中的值和现在的值不一样,那么回到第一步重新自旋,直到改写变量成功为止。对于其他进程改变了变量的值,只是值一样,如果需要考虑这种情况为竞争过,可以为变量值加一个版本号。

轻量级锁升级

  • 线程1获取轻量级锁时会先把锁对象的对象头MarkWord复制一份到线程1的栈帧中创建的用于存储锁记录的空间(称为DisplacedMarkWord),然后使用CAS把对象头中的内容替换为线程1存储的锁记录(DisplacedMarkWord)的地址;

  • 如果在线程1复制对象头的同时(在线程1CAS之前),线程2也准备获取锁,复制了对象头到线程2的锁记录空间中,但是在线程2CAS的时候,发现线程1已经把对象头换了,线程2的CAS失败,那么线程2就尝试使用自旋锁来等待线程1释放锁。

  • 但是如果自旋的时间太长也不行,因为自旋是要消耗CPU的,因此自旋的次数是有限制的,比如10次或者100次,如果自旋次数到了线程1还没有释放锁,或者线程1还在执行,线程2还在自旋等待,这时又有一个线程3过来竞争这个锁对象,那么这个时候轻量级锁就会膨胀为重量级锁。重量级锁把除了拥有锁的线程都阻塞,防止CPU空转。

*注意:为了避免无用的自旋,轻量级锁一旦膨胀为重量级锁就不会再降级为轻量级锁了;偏向锁升级为轻量级锁也不能再降级为偏向锁。一句话就是锁可以升级不可以降级,但是偏向锁状态可以被重置为无锁状态。

重量级锁

重量级锁需要向操作系统申请,程序从用户态-> 内核态 -> 用户态。上重量级锁后,对于没有获得锁的线程,将会根据优先级到锁的队列里进行排队,队列里的线程将会处于睡眠状态,获得锁之后会被操作系统唤醒。对于多个CPU来说,一个CPU可以通过锁住总线来防止其他CPU改写数据。

synchronized 锁升级过程

锁升级过程大致如下


image.png

参考文献

相关文章

网友评论

      本文标题:Synchronized 锁升级过程

      本文链接:https://www.haomeiwen.com/subject/sjonaktx.html