锁膨胀,偏向锁,轻量级锁,重量级锁

本文详细介绍了Java中synchronized的不同锁状态(无锁、偏向锁、轻量级锁和重量级锁),以及锁膨胀的过程,包括偏向锁的激活、状态转换和锁升级机制。着重讲解了偏向锁、轻量级锁的实现原理及它们与重量级锁的区别。
摘要由CSDN通过智能技术生成

1.synchronized
我们都知道synchronized内部有四种状态,分别是:无锁、偏向锁、轻量级锁和重量级锁,所以要搞懂这几种锁之间的变化我们得对synchronized有个大致的了解。

首先说一下synchronized在底层的实现,他是基于进入和退出Monitor对象(每一个对象都会有一个monitor与之关联)来实现方法同步和代码块同步的,对于方法同步官方并没有具体指出是如何实现的,而对于同步代码块是通过monitorenter和monitorexit指令来实现的。在进入同步代码的地方执行monitorenter指令,在代码执行结束和异常处执行monitorexit指令,这样做是为了不至于在出现异常的时候无法释放锁,避免死锁。空口无凭,我们来看一下代码。

public class MonitorTest {
    Object lock = new Object();

    public int test(int i){
        synchronized (lock){
            return i-1;
        }
    }
}

经过反编译之后可以得到



  public int test(int);
    Code:
       0: aload_0
       1: getfield      #3                  // Field lock:Ljava/lang/Object;
       4: dup
       5: astore_2
       6: monitorenter
       7: iload_1
       8: iconst_1
       9: isub
      10: aload_2
      11: monitorexit
      12: ireturn
      13: astore_3
      14: aload_2
      15: monitorexit
      16: aload_3
      17: athrow
}

从上面我们可以看出有一个monitorenter但是却有两个monitorexit,就是为了防止异常时无法释放锁。

2.对象头
synchronized的锁时存放在对象头里的,所以我们得对对象头有一定的了解,这里我们不细讲,可以参考深入理解JVM学习笔记。

我们 synchronized锁的信息是存在对象头里一个叫Mark Word的区域里的 ,在运行期间,Mark Word里存储的数据会随锁状态的变化而发生变化,我们来看一下

下面开始我们今天的正式内容,锁膨胀

3.锁膨胀

首先我们得知道锁时只能升级不能降级的(这里说的是有线程占有锁的情况,如果没有任何一个线程占有锁,锁是可以降级成无锁的),也就是偏向锁升级成轻量级锁就不能再降级后才能偏向锁了,然后我们看一下锁膨胀的流程图。

我们从头开始,一步步讲解锁膨胀的过程。

偏向锁和无锁
我们先介绍一下偏向锁和无锁,偏向锁在Java 6和Java 7是默认启动的,但是在程序启动几秒之后才可以激活,这个值默认是4秒,在这之前创建的对象就是无锁状态,可以通过-XX:BiasedLockingStartupDelay=0来关闭延迟,可以通过JVM参数关闭偏向锁:-XX:-UseBiasedLocking=false。

如果JVM启用偏向锁,那么一个新建未被任何线程获取的对象Mark Word中的Thread Id为0,是可以偏向但未偏向任何线程,被称为匿名偏向状态。而无锁状态是不可偏向也未偏向任何线程,不可再变为偏向锁。记住!无锁状态不能变成偏向锁!

偏向锁有三种状态:

  1. 匿名偏向:这是允许偏向锁的初始状态,其Mark Word中的Thread ID为0,第一个试图获取该对象锁的线程会遇到这种状态,可以通过CAS操作修改Thread ID来获取这个对象的锁。
  2. 可重偏向:这个状态下Epoch是无效的,下一个线程会遇到这种情况,在批量重偏向操作中,所有未被线程持有的对象都会被设置成这个状态。然后在下个线程获取的时候能够重偏向。
  3. 已偏向:这个状态最简单,就是被线程持有着,此时Thread ID为其偏向的线程。

可能有人不理解批量重偏向是什么意思,这里我主要讲解一下,顺便说一下批量撤销。

首先说明一下,批量重偏向和批量撤销都是针对类的优化,与不是针对单个对象。

批量重偏向:当一个线程创建了大量对象(同一个class的)并执行了初始的同步操作,后来另一个线程也来将这些对象作为锁对象进行操作,会导偏向锁重偏向的操作。 具体是多少个对象呢?Hotspot默认是二十个( 当然我们可以通过-XX:BiasedLockingBulkRebiasThreshold 来手动设置阈值 ),意思就是如果线程A创建了二十个以上的对象(相同class的)并获取该对象的偏向锁,在其释放锁后又有一个线程获取这些对象,那么一旦线程B获取对象锁的数量达到二十个,这之后的对象就可以重偏向(之前的是轻量级锁),依旧可以作为偏向锁使用,但是二十个之前的则会升级为轻量级锁。但是一定要注意:**重偏向只能触发一次!**如果又有第三个线程C获取已经重偏向过的锁会直接升级成轻量级锁。

批量撤销:批量撤销就是如果线程B在达到批量重偏向之后继续获取对象锁,这个数量达到了批量撤销阈值(默认是40,可以通过 -XX:BiasedLockingBulkRevokeThreshold 来手动设置阈值),那么JVM就认为当前场景存在多线程竞争,会标记该class不可偏向,之后再对于该class对象的锁直接走轻量级锁流程(即使是新建对象)。注意:触发批量撤销的线程仍然能够使用偏向锁(即使是新建对象),是从下一个线程开始变成轻量级锁.

批量撤销:批量撤销就是如果线程B在达到批量重偏向之后继续获取对象锁,这个数量达到了批量撤销阈值(默认是40,可以通过 -XX:BiasedLockingBulkRevokeThreshold 来手动设置阈值),那么JVM就认为当前场景存在多线程竞争,会标记该class不可偏向,之后再对于该class对象的锁直接走轻量级锁流程(即使是新建对象)。注意:触发批量撤销的线程仍然能够使用偏向锁(即使是新建对象),是从下一个线程开始变成轻量级锁。

参考自偏向锁批量重偏向与批量撤销

重量级锁
重量级锁是通过互斥量(Mutex)来实现的 ,一个线程获取到锁进入同步块,在没有释放锁之前,会阻塞其他未获取锁的线程。

轻量级锁
1. 线程在执行同步块之前,会在栈帧里创建一个存储锁记录(Lock Record)的空间,并把对象头里的Mark Word复制到锁记录里(官方成为Displaced Mark Word),然后JVM会使用CAS操作将对象头里的Mark Word更改为指向锁空间的指针。
2. 如果更新成功了就获取到这个对象的轻量级锁
3. 如果更新失败了首先会检查对象的Mark Word是否指向当前的线程,如果指向当前的线程,说明4. 该线程已经获取这个这个对象的锁了,继续执行同步块代码。
5. 如果不指向当前线程,表示有其他线程竞争锁,当前线程便尝试自旋获取锁。如果在这过程中获取到了,那就执行同步块代码。
6. 如果自旋一定次数还没竞争到锁,就将锁升级为重量级锁,当前线程阻塞。
如果持有线程释放锁失败(CAS替换Mark Word,因为有其他线程在争夺锁),那么将释放锁并唤醒等待的线程


偏向锁——>轻量级锁
1. 线程第一次访问同步代码块,首先他会检查这个对象的Mark Word中的锁标志位是多少,如果是01,则进入第2步,依此来判断是否是无锁状态或者是偏向锁状态,如果00(轻量级锁),则进入第1步,如果是10(重量级锁),则进入第2步。
2. 继续判断是否是偏向锁,如果是偏向锁则进入第3步,如果不是则进入第 步。
3. 接着判断对象Mark Word中的Tread ID是否是当前线程ID,如果是就代表当前线程已经获取到这个对象锁,以后再获取对象锁的时候不需要再进行CAS操作, 只会往当前线程的栈中添加一条Displaced Mark Word为空的Lock Record中,用来记录重入的次数。释放锁时依次删除锁空间里的Lock Record,当记录释放完之后就代表着释放了这个对象的锁。 这里有一点需要注意:偏向锁的释放并不是主动,而是被动的,持有偏向锁的线程执行完同步代码后不会主动释放偏向锁,而是等待其他线程来竞争才会释放锁 ,就是Thread ID依旧不会改变。


4. 如果当前对象Mark Word中的Thread ID不是当前线程,那么就会使用CAS操作修改Thread ID,如果这个对象是匿名偏向状态就可以修改成功,成功获取锁,如果不是就说明对象锁已被其他线程占有,则修改失败,执行第5步。
5. 修改失败后就会进行锁撤销,锁的撤销需要等待一个全局安全点(当前没有执行的字节码),然后暂停拥有这个偏向锁的线程,判断这个线程是否依然存活。如果是执行第6步,否则第7步
6. 如果持有线程依旧存活,判断是否还在执行同步代码块(根据Lock Record),如果还在执行,则升级为轻量级锁,持有线程获取轻量级锁(JVM将对象Mark Word与Lock Record交换),获取线6程CAS竞争获取锁。
7. 如果对象已死亡或不在执行同步代码块,则判断该对象是否可重偏向。如果可以则将对象设置成匿名偏向状态,则将使用CAS操作将偏向锁重新指向当前线程,获取到偏向锁,执行同步代码块。如果不可重偏向,就将对象设置为无锁状态,然后升级为轻量级锁,CAS获取锁。
8. 恢复暂停的线程,继续执行代码
注:持有线程是持有偏向锁的线程,当前线程是想要获取偏向锁的线程

轻量级重量级偏向锁Java中的三种不同的机制。轻量级是一种乐观,用于在多线程环境下提高并发性能。当竞争不激烈时,轻量级使用CAS(Compare and Swap)操作来获取,避免了线程阻塞,提升了性能。然而,如果竞争激烈,轻量级膨胀重量级偏向锁是在没有竞争的情况下,为了进一步提高性能而引入的机制。偏向锁允许第一个获得的线程在接下来的执行中,无需再进行同步操作,从而减少了不必要的竞争。当有其他线程试图获取偏向锁时,偏向锁会升级为轻量级重量级重量级是一种传统的互斥,它使用操作系统原语(例如互斥量)来实现线程阻塞和唤醒,确保同一时间只有一个线程可以访问被定的资源。重量级适用于竞争激烈的场景,但由于涉及到内核态和用户态之间的切换,会带来较大的性能开销。 总结起来,轻量级偏向锁都是为了提高并发性能而引入的机制,轻量级适用于竞争不激烈的场景,而偏向锁适用于没有竞争的情况下。重量级适用于竞争激烈的场景,但会带来较大的性能开销。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [浅谈偏向锁轻量级重量级](https://blog.csdn.net/yaojiqic/article/details/124619021)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 33.333333333333336%"] - *2* [Java中的偏向锁轻量级重量级解析](https://blog.csdn.net/lengxiao1993/article/details/81568130)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 33.333333333333336%"] - *3* [Java的升级策略 偏向锁 轻量级 重量级](https://download.csdn.net/download/weixin_38512659/12749004)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 33.333333333333336%"] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值