美文网首页
Java锁膨胀

Java锁膨胀

作者: 简楼 | 来源:发表于2021-04-17 21:45 被阅读0次

    前言

    到这里,大家应该都知道,Java中都有哪些锁,做什么用的了;

    那么,不知道大家有没有听过Java锁的膨胀机制?

    synchronized

    JDK1.6 前只有重量级锁,JDK1.6之后,jvm对其进行优化,增加了偏向锁和轻量级锁(这两种锁咱们在上一篇已经解释过了,就不在复述了);

    对象头

    了解锁膨胀之前,咱们需要先了解下对象头;

    在JVM的实现中每一个对象都会有一个对象头,它是用于保存对象的系统信息,对象头中有一个官方称为 MarkDown 的部分,他就是实现各种锁的关键;

    在32位系统里,MarkDown 是32位的数据,64位系统中是64位的数据;

    存放了对象的哈希值、对象年龄、锁的指针等等信息,总之就是一个对象的锁是否被占用、占用的是哪种锁,就记录在MarkDown中;

    synchronized 升级之一阶段(偏向锁)

    当线程访问同步代码块并获取锁时,会在 Mark Word 里存储锁偏向的线程 ID;

    在线程进入和退出同步块时不再通过 CAS 操作来加锁和解锁,而是检测 Mark Word 里是否存储着指向当前线程的偏向锁;

    偏向锁只有遇到其他线程尝试竞争偏向锁时,持有偏向锁的线程才会释放锁,线程是不会主动释放偏向锁的;

    偏向锁的撤销:

    1. 在某个时间点上没有字节码正在执行时,它会先暂停拥有偏向锁的线程,然后判断锁对象是否处于被锁定状态
    2. 线程不处于活动状态,则将对象头设置成无锁状态,并撤销偏向锁,恢复到无锁(标志位为01)或轻量级锁(标志位为00)的状态

    JVM 参数:

    关闭偏向锁:-XX:-UseBiasedLocking=false,关闭之后程序默认会进入轻量级锁状态

    synchronized 升级之二阶段(轻量级锁)

    轻量级锁是指当锁是偏向锁的时候,却被另外的线程所访问,此时偏向锁就会升级为轻量级锁,其他线程会通过自旋的形式尝试获取锁,线程不会阻塞,从而提高性能;

    升级过程:

    1. 线程在自己的栈桢中创建锁记录 LockRecord
    2. 将锁对象的对象头中的MarkWord复制到线程的刚刚创建的锁记录中
    3. 将锁记录中的Owner指针指向锁对象
    4. 将锁对象的对象头的MarkWord替换为指向锁记录的指针,并将对象 Mark Word 的锁标志位设置为“00”,表示此对象处于轻量级锁定状态

    出现轻量级锁的两种情况:

    1. 当关闭偏向锁功能时
    2. 多个线程竞争偏向锁导致偏向锁升级为轻量级锁

    轻量级锁: CAS 操作和自旋来解决加锁问题,避免线程阻塞和唤醒而影响性能;

    轻量级锁再次膨胀情况:

    1. 若当前只有一个等待线程,则该线程将通过自旋进行等待。但是当自旋超过一定的次数时,轻量级锁便会升级为重量级锁
    2. 当一个线程已持有锁,另一个线程在自旋,而此时又有第三个线程来访时,轻量级锁也会升级为重量级锁

    synchronized 升级之三阶段(重量级锁)

    重量级锁是依赖对象内部的monitor锁来实现的,而monitor又依赖操作系统的MutexLock(互斥锁)来实现的,所以重量级锁也被成为互斥锁;

    当有一个线程获取重量级锁之后,其余所有等待获取该锁的线程都会处于阻塞状态,操作系统实现线程之间的切换需要从用户态切换到内核态,而转换状态是需要消耗很多时间的,有可能比用户执行代码的时间还要长,所以它的切换成本非常高,这同时也说明了为什么重量级线程开销很大的原因;

    总结

    通过上面的分析,我们知道了 synchronized 锁的演变过程,这就告诉我们,假如一开始就知道某个同步代码块的竞争很激烈,那么一开始就应该使用重量级锁了,节省掉一些锁转换的开销。

    相关文章

      网友评论

          本文标题:Java锁膨胀

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