synchronized锁升级_synchronized锁升级分析

1 Mutex介绍

    Mutex中文名称是互斥锁,跟着中文名称很好理解了,就是为了互斥;

    在并发的情况下,对于一个对象的操作,可能会导致数据不一致性问题,为了保证共享数据操作的完整性。每个对象都对应于一个可称为" 互斥锁" 的标记,这个标记用来保证在任一时刻,只能有一个线程访问该对象;

2 经典问题引入

    前言:synchronized是获取锁对象!!!是一个操作,将锁对象加到代码上面;(这句话多记几遍,不然下面会踩坑) 

    下面这段代码,创建了1000条线程去执行一个static的变量,每一条线程使的count加一,为了使问题更加明显,这里让线程在count++之前,睡眠了一毫秒。在打印输出结果之前,再让主线程睡眠三秒,保证所有线程都执行完再打印数据;原本预期结果是1000,按照正常逻辑也应该是1000.其实不然,每个电脑的结果不同,你自己试着跑一遍吧。

952035bddad435164d1268e4ad991705.png

    打印出来肯定不是1000,根据JMM的模型,可以推断是工作内存的变量没有同步到主内存当中。

22b89fd5402f9c38811b9e00fee22bab.png

采用synchronized给当前对象加锁,打印结果是1000

e8c536a8491fcd61b4286094329cb5e5.png

通过字节码分析,可以看到同步的部分是使用monitorenter和monitorexit指令。这两个指令隐式地执行了mutex的lock和unlock操作,用于提供原子性的操作;

    这两个指令的命名也有有原因的,原则上他们是获取了这个对象的监视器(monitor),这个过程是排他的,也就是说同一时刻只有一个对象能获取到由synthronized保护的对象;

a6cfe38d81cab1f65a638c77b74713fb.png

好了,我们加上synchronized修饰,运行结果是1000毫无疑问的

3 深入了解synchronized

    首先明确一下我们只需要关注对象头的此处位置三个数字,这个地方是对象的锁状态(下面这张图只是举例说明看哪);

20ecb1e83759ad77a178db2d1374632a.png

    然后我们只需关于这些状态

1e65c36e15f9ad36548f519fbde03163.png

先来看下锁升级的流程图   

    在Java SE1.6的时候对于synchronized进行了优化,也就是synchronized在加锁的时候,里面关于锁的机制进行升级,升级的过程如下流程图:

(以下都是加了synchronized的时候进行分析的结果)

当线程第一次访问这个对象时:

d708c1566ee24e942e5054253b80d570.png

解释:线程一访问到synchronized代码块的时候,先检查对象的标记位,第一次进来的线程肯定不会读到标记检查,就将对象占有,进行标记,然后执行代码块,执行完成之后不会清除对象的标记。(这个过程是偏向锁的过程)

当有第二个线程接着访问此对象的时候:

d4ea463c63f54b665dd937109795bc6b.png

解释:当第二个线程访问到synchronized代码块的时候,检查到有标记(因为上一个对象不会清除),判断上一个对象是否存活,如果不存活了,则跟第一个线程进来一样的步骤,如果存活,则进入到轻量级锁,也就是锁自旋;如果自旋太久了了,也就是大于十次了,就转移到重量级锁,将线程挂起;

4 synchronized锁验证

无锁状态:

   e4d60e9345bf092c251d5159ae2c2f68.png

69b08e321d0b1dfaa9302f6d86f3344f.png

偏向锁

a7dcc1cbef9a60b14522247c0c54d4e6.png

4b2dbf48b615a3c1d20e7bddbb105f7e.png

解释:因为JVM底层进行加载的时候,会将延时加载的对象加上偏向锁;

注意:此时的偏向锁是一种特殊的偏向锁,具体往下看

fc5875cbc7a3e3e7e1bf5bec5c998e56.png

在对象头的位置上,我们可以看到线程ID此时其实是全为零的,再结合偏向锁的概念,偏向锁,偏向、偏向...其实就是偏向了某个线程,此时不偏向其他线程,也可以理解这个就是一个特殊的“无锁”;

    那么怎么让他偏向呢?

95a75552c212be7b87f369b81c368ede.png

上图的代码可以让这把锁进行偏向

ddf9f31d8c60c355e3cffb9af32f360e.png

可以看到依然是偏向锁的情况下,有了偏向的线程ID了

注意:你要讲访问的对象加synchronized修饰才有这种效果

自旋锁(轻量级锁):

    自旋锁是自我上锁了,这个自我上锁的条件,是上一个线程还存活,那么就想办法让线程存活的情况下,再执行一条线程;

5ba2c6ed2056018e7e089baf819c8b8b.png

a26d921cd452552b5766ce636dd649b0.png

在主线程不在睡眠直接加载类的情况下,JVM不底层没有触碰到stu的synchronized代码片段,但是在创建第二个线程创建的时候,JVM底层触碰到了stu的synchronized代码片段,导致了中间会变化了偏向锁,然后根据流程图,第二个线程访问这个具有偏向锁,且第一个线程为消亡的情况下(在main线程当中创建了另外一个线程,main线程肯定还不会消亡),会将偏向锁改为轻量级锁(自旋锁)。

ps:JVM中间转换过程目前能力原因找不到方法去证明,这是一个推断的想法。如果你有方法证明,可以联系一下我吗?

重量级锁:

1eb3fbe25f8208d1af6760735c28d36d.png

8a9e796ee5bc3a141d050317976d0d54.png

    自旋状态还没结束,就会导致不断去自旋,自旋次数超过十了,就会将锁机制转换到重量级锁,这个过程可能比较麻烦去验证,这里主要是证明锁的存在,以及验证什么情况会产生不同的锁。

【小结】

    synchronized在这此笔记当中只是简单了验证了锁升级,以及synthronized的简单了解。关于synthronized还有很多很多需要学习的,锁的加锁过程怎么去debug,发现升级过程这些的,还需要好好加强

【题外话】

因为有点好奇延迟加载究竟延迟加载多久才加锁,在我自己电脑进行了测试,在写文章的时候是睡眠了这个值,也就是在我自己电脑上,睡眠了这么长的时间情况下,多运行几次会产生在无锁与偏向锁的切换,有兴趣的小伙伴可以自行尝试,然后在下方留言(这个对我有帮助哦,谢谢你啦)

7f70aa6a879235071720d98a4410e47a.png

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值