浅谈synchronized

4 篇文章 0 订阅

浅谈synchronized

没有系统学习过,导致有些概念其实都是从各种博客,教程上东拼西凑上拼接成的。没办法,以后学习只能靠自己了。

我这里总结的知识点,也只是b站黑马的juc课程。

但是真的讲的不错啊。

先讲现象

我们平常用来处理多线程的共享资源的问题的时候,都是用的synchronize。知道锁实例对象和锁类对象的区别。可是为什么呢。有人说是jvm底层的monitorenter指令。可是为什么这个指令就能做到锁的效果呢。我们以前是知道这个知识,但是不知道为什么。就想我们知道物质是原子构成的,但是物质性质为什么是这样的呢。这里又涉及到原子的电子了。

底层

每个对象有三个数据,mark word(对象头,这样每个对象都可以有专属的状态)。 Klass(指向类类型),属性数据。

syncchronized本来是利用对象头关联moniitor,monitor是jvm维护的数据,里面的结构是waitset,entiylist,owner。

这样通过锁monitor,就能关联对象和线程了。

线程1通过对象去获取锁对象,通过对象头找到moniitor。monitor发现owner没有线程占用,然后cas尝试改写owner,改写成功就获取了锁。

线程2这时候尝试通过对象去获取锁对象,发现owner已经有对象了,然后就进了entiylist阻塞了。

然后线程1从临界区运行完后,cas把owner置为空。然后通知entityList。这样entitylist里thread数组就可以被欢喜争取cpu了。

而保证锁的释放,是通过try,catch保证。

而waitset其实就是我们的object的wait和notify的实现结构了。

这样本来也能执行。但是这样的monitor太重量级了,因为每次新来到临界区,都要去申请一下monitor。维护要成本的啊。

而不是所有时候,都有这么多线程去抢的,然后往往是一个线程自己担心,其实没人抢你。也有就是竞争不是那么强烈。我会和你一起用,但是我们时间不冲突的啊。

所以,jvm优化了synchronize的实现,引入了轻量锁,什么是轻量锁呢,

也是对象头里,指向了一个Lock record地址(也是jvm维护),里面的结构是 hash code (因为你的lock record地址占用了原来的对象头mark word里的hash code数据)和对象地址。

线程1通过对象1获取锁对象,通过对象头找到了lock record。 然后发现暂时没人用过这个对象,那就申请了一个log record。维护了对象和线程的关系,lock record是线程中有的。

线程1又通过对象获取锁,发现已经有lock record了,而且还是自己和这个对象的,那就在申请一个log record。后续通过有多少个log record判断重入了多少锁。

如果这时候没有人来抢锁的话,就是线程1依次以栈的方式,释放lock record。这样最后,对象头又还原成没锁的样子。

如果有线程抢夺。

如果线程2来获取锁,发现哦吼,这个已经申请过lock record的了,然后不是我申请的。怎么办呢。那我自己申请一个monitor,自己进entityLIst等着。owner指向thread1.

然后等线程1执行玩最后,发现要还原对象头的时候,发现对象头变成执行monitor了,那我就把monitor的owner置为空呗,然后通知entitylist呗。

这样就从轻量锁膨胀为重量锁了。

这其实也是有点重了,有没有更简单的呢。 因为大部分,其实就是一个线程自己瞎担心。

这时候,jvm就说,你自己把线程ID(操作系统的线程,不是我们理解的线程id)写在对象头把。

这样,

线程1通过对象1看了下,没人动过这个对象头,那我就cas设置写上我的threadID.

下次来再申请的时候发现是偏向锁,然后一看对象头里的线程ID,发现原来是我用过的,那就不用管了,再进去呗。

这里的话,需要想想,偏向锁怎么升级为轻量锁呢。他设置了线程ID可没擦除啊。其实是这样的,

如果线程2来向对象申请锁,先看是否是可偏向的,如果是不可偏向的,那就说明这个已经是升级为轻量锁了。那就走轻量锁的逻辑。

如果,是可偏向的,然后看线程ID,如果是自己的,那说明是自己以前用过的。

如果不是我的线程ID,这个时候,jvm会stw,撤销偏向锁,撤销之前必须要检查线程A是不是还活着,因为此时线程A可能正在临界区内执行同步代码,也可能线程A活的很滋润。

看线程ID,如果线程ID已经死了,那说明可以用,那我cas写我的线程ID.

然后如果线程ID没死,锁升级为轻量级锁,在暂停的线程栈中创建用于存储锁记录的空间,并将对象头中的Mark Word复制到                 锁记录中,使用CAS将对象头中的Mark Word替换为指向锁记录的指针,恢复之前暂停的线程

JVM线程伪造一个displaced mark word到持有锁的线程的栈上,object的mark work更新为轻量锁模式。然后唤醒持有锁的线程。这样持有锁的线程就会自以为自己持有的是轻量锁了。而竞争者也会按照轻量锁的模式去竞争锁。

细节补充:
如何判断偏向所有者没有正在持有该偏向锁?
分两步,首先判断偏向所有者是否还活着,如果还活着,则遍历它的栈,看是否能找到关联该锁的锁记录,如果找到,则正在持有,如果没找到,则没有持有。(遍历过程在一个安全点执行,此时偏向所有者被阻塞。)

偏向所有者正在持有该偏向锁,如何将其撤销为轻量级锁?
遍历偏向所有者的栈,修改与该锁关联的所有锁记录,让偏向所有者以为它对该对象加的就是轻量级锁。

https://blog.csdn.net/weixin_42213903/article/details/97044043

https://blog.csdn.net/weixin_39568133/article/details/114757317

https://www.zhihu.com/question/57774162

https://www.jianshu.com/p/7445361e187f

这里又有个优化,因为偏向锁被升级为轻量锁,如果只是几个对象,那没事,而如果一定量级的话,那是不是说明,大人,时代变了。这个对象应该被这个对象使用呢。jvm在升级了几个这个类的对象后,会直接把其他的这个类的对象偏向新的对象(对象头的线程ID改为新对象)。

而如果升级了更大量级的话,是不是说明这个类对象竞争很激烈? 这个时候这个类对象直接是从轻量锁申请的。

jvm还会从编译的时候,优化一些没有的申请锁。比如在一个方法里,循环申请锁的话,那为什么不直接在方法上呢。这个就是锁消除。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值