轻量级锁、偏向锁、重量级锁详情

这篇文章是上篇文章是否真的理解了偏向锁、轻量级锁、重量级锁(锁膨胀)、自旋锁、锁消除、锁粗化,知道重偏向吗?的补充,对于偏向锁

 

这篇文章提出了对于一书“深入理解Java虚拟机”中的一张图的深入是否使用偏向锁解读,当然这张图是Oracle官方的,呵呵。还必须具有深入知道偏向锁、轻量级锁、重量级锁(锁膨胀)、自旋锁、锁消除、锁粗化这几个的过程,如果不知道。前面那个传送门可以去看一下,我是从操作系统层的PV操作的角度入手解读轻量级锁和偏向锁的,当然JVM底层实现调用操作系统的api的确是用的那个,针对于Monitor,C没有实现Monitor,Java却实现了,提供给我们很好的Monitor实现工具。像synchronized和ReetrantLock,不过这儿声明,synchronized单个不是Monitor,这儿给个我在一本书上看到Monitor(管程)的定义“管程由一些共享数据、一组能为并发进程所执行的作用在共享数据上的操作的集合、初始代码以及存取权组成。”,从这个上面解读,Monitor还包括很多东西,还和业务数据有关。不过在网上看的很多对于Java中Monitor的解读似乎很不符合这个原则,我也不知道什么原因,可能是英语转换成汉语让人误解的问题吧,这儿不详细讨论这个了。但是知道Monitor是用PV操作实现的。

先给出这张图和一个Mark Word的结构:

锁状态25bit4bit1bit2bit
23bit2bit是否偏向锁锁标志位
无锁态对象的hashCode分代年龄001
轻量级锁指向栈中锁记录的指针00
重量级锁指向互斥量(重量级锁)的指针10
GC标记11
偏向锁线程IDEpoch分代年龄101

  

疑惑:

       1、重偏向是做什么的?

        2、在已经初始锁定的情况下 的偏向锁为什么还有锁定和 解锁?

        3、撤销偏向,怎么知道对象 有没有锁定?

当时为了解除这些疑惑我baidu到了另外的解释:

HotSpot支持存储释放偏向锁,以及偏向锁的批量重偏向和撤销。这个特性可以通过JVM的参数进行切换,而且这是默认支持的。Unlock状态下MarkWord的一个比特位用于标识该对象偏向锁是否被使用或者是否被禁止。如果该bit位为0,则该对象未被锁定;如果该bit位为1,则意味着该对象处于以下三种状态:

  • 匿名偏向(Anonymously biased)
    在此状态下thread_ptr为NULL(0),意味着还没有线程偏向于这个锁对象。第一个试图获取该锁的线程将会面临这个情况,使用原子CAS指令可将该锁对象绑定于当前线程。这是允许偏向锁的类对象的初始状态。
  • 可重偏向(Rebiasable)
    在此状态下,偏向锁的epoch字段是无效的(与锁对象对应的klass的mark_prototype的epoch值不匹配)。下一个试图获取锁对象的线程将会面临这个情况,使用原子CAS指令可将该锁对象绑定于当前线程。在批量重偏向的操作中,未被持有的锁对象都被至于这个状态,以便允许被快速重偏向。
  • 已偏向(Biased)
    这种状态下,thread ptr非空,且epoch为有效值——意味着其他线程正在使用这个锁对象。

基于偏向锁对象需要使用hashcode字段作为偏向线程id标识的事实,被hash的对象不可被用作偏向锁。对于允许偏向的对象在进行hashcode计算时,首先要吊销(revoke)所有的偏向(不管是有效的还是无效的),然后使用CAS将计算好的hashcode值放到MarkWord中,尽管这仅仅适用于“identity hashcode(使用Object类的hashcode()方法进行计算)”。普通Java类型hashcode的计算需要重载Object的hashcode()方法,但不必要去显示调用这个方法;因此,对于没有显示调用Object#hashcode()方法的类的对象,仍然适用于偏向锁的机制——可被用作锁对象使用。

HotSpot为所有加载的类型,在class元数据——InstanceKlass中保留了一个MarkWord原型——mark_prototype。这个值的bias位域决定了该类型的对象是否允许被偏向锁定。与此同时,当前的epoch位也被保留在prototype中。这意味着,对应class的新对象可以简单地直接拷贝这个原型值,而不必在后面进行修正。在批量重偏向(bulk rebias)的操作中,prototype的epoch位将会被更新;在批量吊销(bulk revoke)的操作中,prototype将会被置成不可偏向的状态——bias位被置0。

偏向锁的获取依靠原子CAS指令将线程指针插入MarkWord中。其先决条件是:1.该对象处于匿名偏向状态;2.该对象处于可重偏向状态(一个锁对象仅能被一个线程偏向一次)。只要锁对象被偏向,递归锁定和解锁仅仅需要读取对象头以及对应Klass的prototype去验证偏向是否被吊销。

HotSpot中偏向锁的撤销是JVM处于在全局安全点时被执行的。在撤销过程中,撤销者会遍历当前偏向线程的锁记录,藉此推断对象当前是否被锁定。如果发现锁对象被一个偏向线程持有,锁记录将被修改——如同轻量级锁被使用一样;如果锁对象未被持有,这是取决于触发撤销的原因,锁对象要么被禁止用作偏向锁,要么被禁止重新偏向于撤销线程。

即使偏向锁的特性被打开,出于性能(启动时间)的原因在JVM启动后的的头4秒钟这个feature是被禁止的。这也意味着在此期间,prototype MarkWord会将它们的bias位设置为0,以禁止实例化的对象被偏向。4秒钟之后,所有的prototype MarkWord的bias位会被重设为1,如此新的对象就可以被偏向锁定了。

获取偏向锁的步骤:

  1. 验证对象的bias位
    如果是0,则该对象不可偏向,应该使用轻量级锁算法。
  2. 验证对象所属InstanceKlass的prototype的bias位
    确认prototype的bias为是否被设置。如果没有设置,则该类所有对象全部不允许被偏向锁定;并且该类所有对象的bias位都需要被重置,使用轻量级锁替换。
  3. 校验epoch位
    校验对象的MarkWord的epoch位是否与该对象所属InstanceKlass的prototype的MarkWord的epoch匹配。如果不匹配,则表明偏向已过期,需要重新偏向。这种情况,偏向线程可以简单地使用原子CAS指令重新偏向于这个锁对象。
  4. 校验owner线程
    比较偏向线程ID与当前线程ID。如果匹配,则表明当前线程已经获得了偏向,可以安全返回。如果不匹配,对象锁被假定为匿名偏向状态,当前线程应该尝试使用CAS指令获得偏向。如果失败的话,就尝试撤销(很可能引入安全点),然后回退到轻量级锁;如果成功,当前线程成功获得偏向,可直接返回。

从上面,我对于

  • 匿名偏向(Anonymously biased)
    在此状态下thread_ptr为NULL(0),意味着还没有线程偏向于这个锁对象。第一个试图获取该锁的线程将会面临这个情况,使用原子CAS指令可将该锁对象绑定于当前线程。这是允许偏向锁的类对象的初始状态。
  • 可重偏向(Rebiasable)
    在此状态下,偏向锁的epoch字段是无效的(与锁对象对应的klass的mark_prototype的epoch值不匹配)。下一个试图获取锁对象的线程将会面临这个情况,使用原子CAS指令可将该锁对象绑定于当前线程。在批量重偏向的操作中,未被持有的锁对象都被至于这个状态,以便允许被快速重偏向。
  • 已偏向(Biased)
    这种状态下,thread ptr非空,且epoch为有效值——意味着其他线程正在只有这个锁对象。

疑惑了,疑惑如下:

     4、可重偏向这个状态是做什么的,为什么 Epoch的字段无效,还有就是epoch字段有什 么作用?

对于这个过程:

  1. 验证对象的bias位
    如果是0,则该对象不可偏向,应该使用轻量级锁算法。
  2. 验证对象所属InstanceKlass的prototype的bias位
    确认prototype的bias为是否被设置。如果没有设置,则该类所有对象全部不允许被偏向锁定;并且该类所有对象的bias位都需要被重置,使用轻量级锁替换。
  3. 校验epoch位
    校验对象的MarkWord的epoch位是否与该对象所属InstanceKlass的prototype的MarkWord的epoch匹配。如果不匹配,则表明偏向已过期,需要重新偏向。这种情况,偏向线程可以简单地使用原子CAS指令重新偏向于这个锁对象。
  4. 校验owner线程
    比较偏向线程ID与当前线程ID。如果匹配,则表明当前线程已经获得了偏向,可以安全返回。如果不匹配,对象锁被假定为匿名偏向状态,当前线程应该尝试使用CAS指令获得偏向。如果失败的话,就尝试撤销(很可能引入安全点),然后回退到轻量级锁;如果成功,当前线程成功获得偏向,可直接返回。

疑惑,疑惑如下:

      5、为什么校验epoch位,如果 InstanceKlass的prototype的MarkWord的 Epoch和对象的MarkWord的epoch位不 匹配表示 偏向已过期,可以重新偏向?

      6、为什么在校验owner线程,比较偏向 线程ID与当前线程ID如果不匹配,还要 尝试使用CAS指令获取偏向,并且如果 成功了还表示偏向成功?

根据这两个还是没有从这些疑惑中找到相同点,寻找到合理的理解,公司可以翻墙,所以我google了下,查到一篇博客:

    https://pdfs.semanticscholar.org/edf9/54412a9b1ce955bea148199f325759779540.pdf

从中知道了原来在偏向锁“可重偏向”是为了“在一个时间段内每一个时刻都是只有一个线程使用同一个对象,但是不是每一时刻都是同一个线程”而。

  • 到全局安全点的时候才停止所有线程。。
  • 增加类型C的epoch的值 定位当前阻塞的线程锁住了的类型C的实例对象并且修改他们的偏向epoch或者撤销偏向。
  • 释放在安全点阻塞的线程。

疑惑:

      7、这个里面为什么 有类型的epoch和 实例对象的epoch?

      8、为什么要自增类型C的epoch的值?

      9、为什么只更新当前阻塞的线程锁住了的类型 C的实例对象的epoch?

最后详细的品味,详细的想终于将一切想通了。

总结疑惑:

1、重偏向是做什么的?

2、在已经初始锁定的情 况下的偏向锁为什么还 有锁定和解锁?

3、撤销偏向,怎么知道 对象有没有锁定?

4、可重偏向这个状态是 做什么的,为什么Epoch 的字段无效,还有就是 epoch字段有什么作用?

5、这个里面为什么 有类型的epoch和实 例对象的epoch?

6、为什么要增加类 型C的epoch的值?

7、为什么只更新当 前阻塞的线程锁住了 的类型C的实例对象 的epoch?

8、为什么校验epoch位,如果 InstanceKlass的prototype的MarkWord的 Epoch和对象的MarkWord的epoch位不 匹配表示 偏向已过期,可以重新偏向。

9、为什么在校验owner线程,比较偏向 线程ID与当前线程ID如果不匹配,还要 尝试使用CAS指令获取偏向,并且如果 成功了还表示偏向成功?

解决:

1、重偏向不是针对“ 在一个时间段内每一个 时刻都是相同线程使用 同一个对象”的情景,而 是在不同时间不同线程 使用相同对象的情景, “重偏向” 是就是为了 更新线程ID。

2、初始锁定也就是锁定,当然也有解锁。标志当前对象是否被线程占用。

3、不是有个锁定和解锁吗,它就是通过这个知道的。

4、可重偏向这个状态也 是针对“不同时间不同线 程使用相同对象的情景” 的情景,当对象中的epoch 和InstanceKlass中的epoch 值不相同的时候,说明这 个对象没有其它线程使用 了,所以对象的epoch字 段无效,下一个线程可以 继续使用,并且偏向。 Epoch其实就是为了标志 对象是否被线程锁住。

5、因为需要知道有 哪些类型C的实例对象 被阻塞的线程锁住 了,因为满足这个条 件的说明是活动的, 如果其他线程在这个 时候来发生竞争,那 么就CAS失败,进入 轻量级锁。

6、因为为了让上次 锁住的对象但是这次 已经不再有线程偏向 区别开。

7、这个问题上面 已经回答了,因为 阻塞的就是正在发 生偏向的,偏向还 没有失效。失效了 的可以重偏向。

8、校验epoch位是为了知道该对象 是否处于活动状态,如果确认和 InstanceKlass不相同,说明上次锁住这 个对象偏向的线程已经不再存活,可以 重新偏向了。

9、因为校验ower线程,比较当前线程 ID和对象里面的ID如果不相等,还有 可能可以发生重偏向,它的场景是“ 在一个时间段内每一个时刻都是只 有一个线程使用同一个对象,但是不 是每一时刻都是同一个线程”。

下面是我在网上找到的流程图,这里面完美总结了,只不过倒数3位不但是否偏向,而且表示是否禁用偏向:

下面附上偏向锁升级为轻量级锁的步凑 :

      1、到全局安全点的时候才停止所有线程。

     2、偏移所有者的堆栈被遍历,并且这个锁记录锁关联的对象将被 填充成如果这个对象发生轻量级锁生成的值。

     3、对象的标记字被更新为指向堆栈上最古老的关联锁记录 。

     4、释放在安全点阻塞的线程。

偏向锁没有那么简单

  • 4
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 13
    评论
轻量级重量级偏向锁是Java中的三种不同的机制。轻量级是一种乐观,用于在多线程环境下提高并发性能。当竞争不激烈时,轻量级使用CAS(Compare and Swap)操作来获取,避免了线程阻塞,提升了性能。然而,如果竞争激烈,轻量级会膨胀为重量级偏向锁是在没有竞争的情况下,为了进一步提高性能而引入的机制。偏向锁允许第一个获得的线程在接下来的执行中,无需再进行同步操作,从而减少了不必要的竞争。当有其他线程试图获取偏向锁时,偏向锁会升级为轻量级重量级重量级是一种传统的互斥,它使用操作系统原语(例如互斥量)来实现线程阻塞和唤醒,确保同一时间只有一个线程可以访问被定的资源。重量级适用于竞争激烈的场景,但由于涉及到内核态和用户态之间的切换,会带来较大的性能开销。 总结起来,轻量级偏向锁都是为了提高并发性能而引入的机制,轻量级适用于竞争不激烈的场景,而偏向锁适用于没有竞争的情况下。重量级适用于竞争激烈的场景,但会带来较大的性能开销。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [浅谈偏向锁轻量级重量级](https://blog.csdn.net/yaojiqic/article/details/124619021)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 33.333333333333336%"] - *2* [Java中的偏向锁轻量级重量级解析](https://blog.csdn.net/lengxiao1993/article/details/81568130)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 33.333333333333336%"] - *3* [Java的升级策略 偏向锁 轻量级 重量级](https://download.csdn.net/download/weixin_38512659/12749004)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 33.333333333333336%"] [ .reference_list ]

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值