偏向锁理论太抽象,实战了解下偏向锁如何发生以及如何升级实战篇(代码片段)

烟花散尽13141 烟花散尽13141     2022-10-21     451

关键词:

锁升级

  • 上文我们主要介绍什么是偏向锁,轻量级锁,重量级锁。并分析了三者的区别和使用场景。还记得Redis章节中整数集中升级操作吗。在锁中我们同样是设计锁升级和降级的。上文我们也介绍了当没有竞争时偏向锁,出现竞争时就轻量级锁。
  • 但是轻量级锁时cas操作和自旋等待。自旋只能适合并发少的情况,如果并发很多一个线程可能需要等待很久才能获取到锁,那么自旋期间的开销也是很巨大的,所以就很有必要升级轻量级锁。那么什么时候该升级重量级锁呢?JVM中也是设置了自旋次数的,超过一定次数就会发生升级成重量级锁

偏向锁升级轻量级锁

  • 个人认为重点还是偏向锁升级的过程。因为偏向锁不会主动撤销,所以锁升级过程涉及批量锁撤销,批量锁偏向等场景。

  • 还记得偏向锁在锁对象的markword中的存储结构吗,末尾三位是101表示偏向锁。关于Lock Record就是上面我们提到的线程栈顶的锁记录对象的指针,关于锁记录内部存储了整个锁对象的markword , 而这里我们需要注意的是EPOCH , EPOCH翻译过来是纪元的意思。我们简单理解成版本好
  • 说到版本号,我们还得熟悉JVM关于偏向锁的两个属性设置

  • 发生轻量级锁升级的时候就会发生偏向锁的撤销。如果JVM发现某一类锁发生锁撤销的次数大于等于-XX:BiasedLockIngBulkRebiasThreshold=20时,就会宣布偏向锁失效。让偏向锁失效就是将版本号加1 即 EPOCH+1;
  • 当一个类锁发生的总撤销数大于等于-XX:BiasedLockingBulkRevokeThreshold=40,则后续在上锁会默认上轻量级锁。
class Demo
    String userName;

public class LockRevoke 
    public static void main(String[] args) throws InterruptedException 
        List<Demo> list = new ArrayList<>();
        for (int i = 0; i < 100; i++) 
            list.add(new Demo());
        
        final Thread t1 = new Thread(new Runnable() 
            @SneakyThrows
            @Override
            public void run() 
                for (int i = 0; i < 99; i++) 
                    Demo demo = list.get(i);
                    synchronized (demo) 
                    
                
                TimeUnit.SECONDS.sleep(100000);
            
        );

        final Thread t2 = new Thread(new Runnable() 
            @SneakyThrows
            @Override
            public void run() 
                synchronized (list.get(99)) 
                    System.out.println("第100个对象上锁中,并持续使用该对象" + ClassLayout.parseInstance(list.get(99)).toPrintable());
                    TimeUnit.SECONDS.sleep(99999);
                
            
        );

        final Thread t3 = new Thread(new Runnable() 
            @Override
            public void run() 
                for (int i = 0; i < 40; i++) 
                    Demo demo = list.get(i);
                    synchronized (demo) 


                        if (i == 18) 
                            System.out.println("发送第19次锁升级,list.get(18)应该是轻量级锁" + ClassLayout.parseInstance(list.get(18)).toPrintable());
                        
                        if (i == 19) 
                            System.out.println("发送第20次锁升级,会发生批量重偏向;纪元+1;后续偏向锁都会偏向当前线程;list.get(19)应该是轻量级锁" + ClassLayout.parseInstance(list.get(19)).toPrintable());
                            System.out.println("因为第100对象仍然在使用,需要修改起纪元" + ClassLayout.parseInstance(list.get(99)).toPrintable());
                        
                        if (i == 29) 
                            System.out.println("在批量重偏向之后;因为第一次偏向锁已经失效了,所以这里不是轻量级而是偏向该线程的偏向锁" + ClassLayout.parseInstance(list.get(29)).toPrintable());
                        
                        if (i == 39) 
                            System.out.println("发送第40次锁升级,发生批量锁撤销;这里应该是轻量级锁后续都是轻量级" + ClassLayout.parseInstance(list.get(39)).toPrintable());
                        
                    
                

            
        );
        
        t1.start();
        t2.start();
        TimeUnit.SECONDS.sleep(5);
        System.out.println("第一次上锁后list.get(0)应该偏向锁:" + ClassLayout.parseInstance(list.get(0)).toPrintable());
        System.out.println("第一次上锁后list.get(19)应该偏向锁:" + ClassLayout.parseInstance(list.get(19)).toPrintable());
        System.out.println("第一次上锁后list.get(29)应该偏向锁:" + ClassLayout.parseInstance(list.get(29)).toPrintable());
        System.out.println("第一次上锁后list.get(39)应该偏向锁:" + ClassLayout.parseInstance(list.get(39)).toPrintable());
        System.out.println("第一次上锁后list.get(99)应该偏向锁:" + ClassLayout.parseInstance(list.get(99)).toPrintable());
        t3.start();
       

    


  • 上面就是典型的偏向锁重偏向和偏向锁撤销案列整合。
  • 首先我们t1线程率先将前99个对象都上锁并立马释放,因为我们的vm设置取消偏向锁延迟了,如何设置请看文章开头部分。
  • 第2个线程t2只对最后一个对象进行上锁,不同的是上锁后永久占着不释放。那么别人就无法获取到最后一个对象的锁
  • 第3个线程开始和上面初始化好的对象进行抢占资源。第三个线程只循环了40次,因为JVM默认的最大撤销偏向锁次数就是40次。后面都是轻量级锁了。
  • 因为第3个线程会发生批量重偏向,所以后续不会造成偏向锁撤销。如果像看到批量锁撤销,就必须在开一个线程上锁。所以线程4就是继续造成撤销,但是要保证线程4后执行,否则t3,t4同时执行会造成重量级锁,因为重量级锁的场景之一就是:1个偏向锁,1个轻量级锁,1个正在请求就会出发重量级锁
  • 在第三个线程中对i==18即第19个元素进行上锁时,因为之前已经被上了偏向锁,虽然被释放了锁,但是偏向锁本身并不会释放,这个前面也已经铺垫了。所以此时第19个元素先发生锁撤销,然后在上轻量级锁。所以这里预测第19个对象时轻量级锁
  • 然后来到i19,即第20个元素,因为JVM默认类总撤销大于等于20会发生批量重偏向。啥意思呢?在t3 中i19之前上锁都是轻量级。i19之后在上锁就会时偏向锁,只不过是偏向线程3的,而不是偏向线程1的。这里我们可以和第一次的i19内存布局进行对比,除了线程id不一样还有一个纪元不一样,

  • 上面为什么我会单独起一个线程锁定list.get(99)呢?就是为了测试当发生批量重偏向的时候能够直观看到正在使用的锁纪元信息被修改,以免造成锁丢弃

  • 我们能够看的出来在发生批量重偏向的时候,正在使用的锁纪元信息会被更新,如果不更新会被JVM认为是废弃偏向锁。当然发生批量重偏向后再次获取对象锁就不会在发生锁撤销了。因为之前的锁已经废弃了,所以我们获取一下后续的锁信息,这里就看看list.get(29)吧。

  • 第4个线程在第三个线程之后不断造成撤销,将达到撤销总数40的时候,JVM就会认为后续该类的锁不适合做偏向锁了,直接就是轻量级锁

偏向锁理论太抽象,实战了解下偏向锁如何发生以及如何升级实战篇(代码片段)

锁升级上文我们主要介绍什么是偏向锁,轻量级锁,重量级锁。并分析了三者的区别和使用场景。还记得Redis章节中整数集中升级操作吗。在锁中我们同样是设计锁升级和降级的。上文我们也介绍了当没有竞争时偏向锁&#x... 查看详情

偏向锁10连问,被问懵圈了。。

前言对于HotpotJVM中的偏向锁,大部分开发者都比较熟悉或者至少听说过。那我们用下面10个关于偏向锁的进阶问题,检验一下自己离精通还有多远。如何判断当前锁对象为偏向锁偏向锁如何判断锁重入当代码运行至synchronized修饰... 查看详情

2.偏向锁,轻量锁,重量锁

参考技术A1.偏向锁,我们在保证线程安全的的情况下,实际情况下,不一定有互斥,如果锁对象没有其他竞争资源,则操作系统默认会为偏向锁,锁总是同一个线程持有,很少发生竞争”,也就是说锁总是被第一个占用他的线程... 查看详情

偏向锁原理

参考技术A本文介绍偏向锁相关原理,并不限定于Java中的偏向锁,但是Java中偏向锁的实现也是相同的原理,本文主要是对参考文献(QuicklyReacquirableLocks)中偏向锁实现重点部分的翻译,加入了自己的理解,参考文献称偏向锁为可快... 查看详情

偏向锁10连问,被问懵圈了。。(代码片段)

前言对于HotpotJVM中的偏向锁,大部分开发者都比较熟悉或者至少听说过。那我们用下面10个关于偏向锁的进阶问题,检验一下自己离精通还有多远。如何判断当前锁对象为偏向锁偏向锁如何判断锁重入当代码运行至synchroni... 查看详情

偏向锁+自旋锁+轻量级锁??????

...储GC标记,对象年龄,对象Hash,锁信息(锁记录的指针,偏向锁线程的ID)大部分情况是没有竞争的,所以可以通过偏向来提高性能所谓的偏向,即锁会偏向于当前已经占有锁的线程 ,通过将对象头Mark的标记设置为偏向,并... 查看详情

偏向锁浅析(代码片段)

  偏向锁不像自旋锁、读写锁或者synchronize修饰词这样的同步,它其实是JVM内置的一种锁机制,自JDK1.6后默认启用。换句话说,这种锁不是咱程序员能用代码来瞎操心的,JVM自己会去操心的。真想要瞎操心,就得改JVM的启动参... 查看详情

偏向锁

无锁竞争的情况下为了减少锁竞争的资源开销,引入偏向锁。 查看详情

22(续01)偏向锁的重入以及线程1获取偏向锁并释放线程2获取锁的调试(代码片段)

...,因此今晚[2022.01.28]花了一些时间来记录 该评论来自于 偏向锁的重入以及线程1获取偏向锁并释放线程2获取锁的调试评论的具体信息如下 SpectacuLarNow:有进行多次测试吗?我多次测试发现,后面进行的线程有事可以获取... 查看详情

java偏向锁实现原理(biasedlocking)

...的一篇博文:Java轻量级锁原理详解(LightweightLocking)Java偏向锁(BiasedLocking)是Java6引入的一项多线程优化。它通过消除资源无竞争情况下的同步原语,进一步提高了程序的运行性能。轻量级锁也是一种多线程优化,它与偏向锁的... 查看详情

开启偏向锁一定性能更好吗?(代码片段)

一、背景最近工作中遇到由于使用偏向锁导致性能下降的案例。趁机总结下偏向锁的概念和锁的升级过程,以及重点聊下偏向锁是否会让性能更优化。二、偏向锁偏向锁是Java6之后加入的一种针对加锁操作的优化手段,它... 查看详情

java偏向锁轻量级锁和重量级锁

...同步代码块和同步方法锁对象和MarkWord重量级锁轻量级锁偏向锁使用场景结语参考链接前言最开始听到偏向锁、轻量级锁和重量级锁的概念的时候,我还以为是Java中提供了相应的类库来实现的,结果了解后才发现,这三个原来是... 查看详情

偏向锁,轻量级锁

 偏向锁偏向锁也是JDK1.6中引入的一项锁优化,它的目的是消除数据在无竞争情况下的同步原语,进一步提高程序的运行性能。如果说轻量级锁是在无竞争的情况下使用CAS操作去消除同步使用的互斥量,那偏向锁就是在无竞争... 查看详情

synchronized的实现原理以及锁升级详解

...ava对象头​​​​锁的升级与对比​​​​无锁​​​​偏向锁​​​​偏向锁的撤销​​​​关闭偏向锁​​​​轻量级锁​​​​轻量级锁加锁​​​​轻量级锁解锁​​​​锁的优缺点对比​​​​锁的验证​​​​引入... 查看详情

为什么95%的java程序员人,都是用不好synchronized?(代码片段)

Synchronized锁优化锁优化偏向锁偏向锁是Java6之后加入的新锁,它是一种针对加锁操作的优化手段,经过研究发现,在大多数情况下,锁不仅不存在多线程竞争,而且总是由同一线程多次获得,因此为了减少同一线程获取锁(会涉及... 查看详情

synchronized偏向锁升级(代码片段)

网上对于 Synchronized偏向锁升级真的是错误多多,漏洞多多。包括网上的一些公开课也是讲的很浅。好在我找到一篇不错的文章,特此记录下https://www.jianshu.com/p/4758852cbff4给大佬跪了,真的牛逼。一 偏向锁的获取  首先... 查看详情

synchronize偏向锁底层实现原理(代码片段)

1偏向锁的意义无多线程竞争时,减少不必要的轻量级锁执行路径。大多数情况下,锁不仅不存在多线程竞争,而且总是由同一条线程去多次获得锁,为了让线程获得锁的性能代价更低而引入了偏向锁。偏向锁主要... 查看详情

java并发之彻底搞懂偏向锁升级为轻量级锁

网上有许多讲偏向锁,轻量级锁的文章,但对偏向锁如何升级讲的不够明白,有些文章还相互矛盾,经过对jvm源码(biasedLocking.cpp)的仔细分析和追踪,基本升级过程有了一个清晰的过程,现将升级流程阐述如下:   因为偏向锁... 查看详情