美文网首页Java架构技术进阶
宝藏图带来了一系列问题:轻量级锁释放为什么唤醒了其他线程?

宝藏图带来了一系列问题:轻量级锁释放为什么唤醒了其他线程?

作者: 代码搬运者Java | 来源:发表于2020-06-29 16:24 被阅读0次

背景

看一看synchronized的升级原理,结果挖到了这样一个宝藏图:

宝藏图带来了一系列问题:轻量级锁释放为什么唤醒了其他线程?

其中有一个不解,就是最后的轻量级锁的释放,这里为什么唤醒了其他线程?很多地方锁这里释放失败之后会膨胀为重量级锁?

遗憾的是

上图好像在偏向锁到轻量级锁的时候少了一步是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。

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

膨胀流程图

轻量级锁

宝藏图带来了一系列问题:轻量级锁释放为什么唤醒了其他线程?

偏向锁

宝藏图带来了一系列问题:轻量级锁释放为什么唤醒了其他线程?

为什么要拷贝mark word?

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

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

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

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

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

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

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

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

最后这里说的挺好的重量级锁使除了拥有锁的线程以外的线程都阻塞,防止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 代码中的一个线程可能暂停执行的位置

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

拾遗

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

如果检查对象的Mark Word不指向当前线程的栈帧,则进入步骤6;

宝藏图带来了一系列问题:轻量级锁释放为什么唤醒了其他线程?

相关文章

  • 宝藏图带来了一系列问题:轻量级锁释放为什么唤醒了其他线程?

    背景 看一看synchronized的升级原理,结果挖到了这样一个宝藏图: 其中有一个不解,就是最后的轻量级锁的释...

  • Condition源码解析

    Condition是JUC里面提供于控制线程释放锁, 然后进行等待其他获取锁的线程发送 signal 信号来进行唤...

  • sleep,wait, join yield

    锁池:所有需要竞争同步锁的线程都会放在锁池中,当一个线程得到锁后,其他线程都会在锁池中等待,当线程释放锁之后,其他...

  • (引用计数表和weak表)散列表

    散列表 自旋锁:忙等,如果锁已被其他线程获取,那么当前线程会自己去不断的获取是否被释放,直到其他线程释放,适用于轻...

  • sleep()、 wait() 、yield() 、join()

    1、sleep()sleep()让当前线程进入阻塞状态,不会释放“锁” 2、 wait()会释放掉锁,让其他线程能...

  • 系统

    锁 互斥锁 加锁如果已经有其他线程加锁了,则阻塞,如果有多个线程在等待锁的释放,在锁释放的时候,所有线程都会被唤醒...

  • sleep()和wait()方法的区别

    sleep()睡眠时,保持对象锁,仍然占有该锁;其他线程无法访问 而wait()睡眠时,释放对象锁。其他线程可以访问

  • 偏向锁,轻量级锁

    1.偏向锁:引入偏向锁是为了在无多线程竞争的情况下尽量减少不必要的轻量级锁执行路径,因为轻量级锁的获取及释放依赖多...

  • 深入理解AQS(二)- 共享模式

    共享锁与独占锁 独占锁被某个线程持有时,其他线程只能等待当前线程释放后才能去竞争锁,而且只有一个线程能竞争锁成功。...

  • java提高班2-Synchronized 加锁原理

    锁升级过程 无锁 -> 偏向锁 -> 轻量级锁 -> 重量级锁 锁升级原理图 无锁 -> 偏向锁 线程T1请求sy...

网友评论

    本文标题:宝藏图带来了一系列问题:轻量级锁释放为什么唤醒了其他线程?

    本文链接:https://www.haomeiwen.com/subject/qitafktx.html