宝藏图,引起的一堆问题,轻量级锁的不解(Synchronized锁的升级)~


背景

看一看synchronized的升级原理,结果挖到了这样一个宝藏图:
在这里插入图片描述
其中有一个不解,就是最后的轻量级锁的释放,这里为什么唤醒了其他线程?很多地方锁这里释放失败之后会膨胀为重量级锁?

最后向画这个图的人多学习
大神的博客网址:https://blog.dreamtobe.cn/
博客签名:

I will have a life no remorse, in the future, we must accomplish something.
我将一生没有悔恨,在未来,我们一定要有所成就。

遗憾的是

上图好像在偏向锁到轻量级锁的时候少了一步是owner指针指向锁对象,不过我想最主要的还是标注的那三步吧。

线程1释放的时候,为什么失败的时候,是唤醒被挂起的那些线程?

因为失败的话,此时锁已经膨胀了。

线程1运行的时候,线程2进来了,这个时候线程2进行cas替换指针的时候失败,所以线程2自旋操作,达到一定此时已然没有成功,所以线程2,会导致锁膨胀成重量级锁,而可能自身(线程2)会被挂起。

线程1释放的时候,已经膨胀成重量级锁了,而重量级锁会导致mark word指向重量级锁monitor,改变了锁对象中的mark word的值,所以线程1释放的时候通过cas操作尝试将Displaced mark word 换回到object mark word失败了,所以需要唤醒他们重新在重量级锁中进行竞争。

但是我想,可能存在例外,如果在线程2自旋的过程中(还没有到达最大的自旋次数)这个时候没有膨胀成重量级锁,所以这个时候如果线程1释放的话,所以是可以释放成功的,而线程2也还在自旋,没有被挂起,所以不需要唤醒线程2

也是想了好久,最后在还是在书中找到了答案。

在《Java并发编程的艺术》一书中的第15页中的–轻量级锁及膨胀流程图,得到了答案

膨胀流程图

轻量级锁

在这里插入图片描述

偏向锁
在这里插入图片描述

为什么要拷贝mark word?

其实很简单,原因是为了不想在lock与unlock这种底层操作上再加同步。

在拷贝完object mark word之后,JVM做了一步交换指针的操作,即流程中第一个橙色矩形框内容所述。
将object mark word里的轻量级锁指针指向lock record所在的stack指针,作用是让其他线程知道,该object monitor已被占用。
lock record里的owner指针指向object mark word的作用是为了在接下里的运行过程中,识别哪个对象被锁住了。

概括,通过cas

https://www.bbsmax.com/A/A2dmM7lbde/

疑问?轻量级锁到底是两条以上的线程还是两条线程争抢同一个锁会膨胀为重量级锁?

之前看博客,多出提到如果有两条以上的线程争用同一个锁,那轻量级锁就不再有效,要膨胀为重量级锁,,但是上面图片中能够看到命名是两条线程就可以膨胀为重量级锁。

查阅书籍,在《深入理解Java虚拟机中》也是这么说我,好吧我猜的没错,像多个博客中写的一模一样的话,大多数都是从书上抄过来的。

如果有两条以上的线程争用同一个锁,那轻量级锁就不再有效,要膨胀为重量级锁,锁标志的状态值变为”10”,Mark Word中存储的就是指向重量级(互斥量)的指针。

在一篇博文中看到了和我一样的困惑,很多人支持这样的观点,暂时引用过来,当做参考,因为,我确实没有查到其他更好的答案,可能这里确实有歧义吧,暂时不钻牛角尖了。

轻量级锁认为竞争存在,但是竞争的程度很轻,一般两个线程对于同一个锁的操作都会错开,或者说稍微等待一下(自旋),另一个线程就会释放锁。 但是当自旋超过一定的次数,或者一个线程在持有锁,一个在自旋,又有第三个来访时,轻量级锁膨胀为重量级锁,重量级锁使除了拥有锁的线程以外的线程都阻塞,防止CPU空转。

原文链接:https://blog.csdn.net/choukekai/article/details/63688332

最后这里说的挺好的重量级锁使除了拥有锁的线程以外的线程都阻塞,防止CPU空转。

总结:

从我目前掌握的来说确实是两个,但是教科书中这么说应该是有道理的,暂且理解为:三条线程,一个持有锁,一个自旋很久(为什么没有考虑到自适应自旋呢,无语),第三个线程来了,这个时候虚拟机告知自己,这种情况必定是有一个自旋了很久,所以判断应该膨胀了,我们知道的那种方式不就是自旋n次(默认10次)获取不到锁,就退出吗?这么做都是为了保护锁。

到底什么时候膨胀为重量级锁?

  1. 自旋n次(默认10次,jdk1.6后有自适应自旋锁)获取不到锁,这个时候应该是有两条线程
  2. 三条线程,一个持有锁,一个自旋很久,第三个线程来了,这时候进行膨胀
  3. 轻量级锁释放锁的时候,这个严格来说应该是和第1条一样,因为这种情况是:假如当前线程1释放,线程2在线程1执行的过程中,自旋n次失败了,挂起自己,并膨胀为重量级锁,因为膨胀为了重量级锁,所以mark word指向重量级锁monitor指针,线程1进行cas将repalced mark word 替换会 object mark word的时候失败,所以很多博客说是失败之后膨胀为重量级锁,其实是错误的,在这之前已经膨胀为重量级锁了,所以释放失败了。所以这里本质上,还是线程2自旋失败导致膨胀,即就是第一条。

什么是全局安全点?

在偏向锁释放锁的时候需要在全局安全点释放,什么是全局安全点?

这个时间点上没有字节码正在执行

此时不会执行任何代码

全局安全点(Safe Point):全局安全点的理解会涉及到 C 语言底层的一些知识,这里简单理解 SafePoint 是 Java 代码中的一个线程可能暂停执行的位置。
https://www.jianshu.com/p/0f31a14373a2

嗯不是很理解,字面意思是懂得,先记住吧~

拾遗

如果更新失败,首先检查对象的Mark Word是否指向当前线程的栈帧,如果是则代表是这是一次锁重入,则向当前线程的栈帧中添加一条Displaced Mark Word为null,Object reference字段指向锁对象的Lock Record,用来统计重入的次数,如下图所示。如果检查对象的Mark Word不指向当前线程的栈帧,则进入步骤6;

在这里插入图片描述

https://www.loongzee.com/2019/04/25/JavaSynchronized_2/

拾遗总结:通过再replace添加

发布了553 篇原创文章 · 获赞 3842 · 访问量 276万+

没有更多推荐了,返回首页

©️2019 CSDN 皮肤主题: 大白 设计师: CSDN官方博客

分享到微信朋友圈

×

扫一扫,手机浏览