热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

synchronize偏向锁底层实现原理

1偏向锁的意义无多线程竞争时,减少不必要的轻量级锁执行路径。大多数情况下,锁不仅不存在多线程竞争,而且总是由同一条线程去多次获得锁&#x

1 偏向锁的意义

无多线程竞争时,减少不必要的轻量级锁执行路径。大多数情况下,锁不仅不存在多线程竞争,而且总是由同一条线程去多次获得锁,为了让线程获得锁的性能代价更低而引入了偏向锁。

偏向锁主要用来优化同一线程多次申请同一个锁的竞争,即当对象被当做同步锁并有一个线程抢到了锁时,则在Mark Word设置该线程的线程ID、是否偏向锁设置1、锁标志位设置01等信息,此时的Mark Word 存储的就是偏向锁状态信息。

在:


  • 创建一个线程并在线程中执行循环监听的场景下

  • 或单线程操作一个线程安全集合时

同一线程每次都需获取和释放锁,每次操作都会发生用户态与内核态的切换。

获取偏向锁的场景:

在自己的线程栈生成一条Lock Record,然后Object Reference指向对象头,此时Lock Record与对象头就建立了联系:

① : 先判断Mard Word的Thread ID是否有值


  • 没有,则表示当前资源没有被其他线程占用,把当前线程ID等信息记录到Mark Word(这需CAS,可能多条线程修改Mark Word,需要保证原子性)
  • 有,则表示当前资源被线程占用,需要判断该线程是不是自己
    • 该线程ID是自己的,则表示可重入,直接获取(此时在自己的线程栈中继续生成一条新的Lock Record)
    • 该线程ID不是自己的,说明出现其他线程竞争,当前持有偏向锁的线程就需要撤销了,即当其他线程尝试获取偏向锁才释放锁

轻量级锁的获取及释放依赖多次的CAS操作,而偏向锁只依赖一次CAS置换ThreadID

一旦出现多个线程竞争时必须撤销偏向锁,所以:

撤销偏向锁消耗的性能必须 < 之前节省下来的CAS原子操作的性能消耗

不然得不偿失!

JDK6默认开启偏向锁,可通过-XX:-UseBiasedLocking禁用偏向锁。


2 偏向锁的获取

偏向锁的入口,synchronizer.cpp 文件的


ObjectSynchronizer::fast_enter

BiasedLocking::revoke_and_rebias实现


2.1 markOop mark = obj->mark()

获取对象的markOop数据mark,即对象头的Mark Word


2.2 判断mark是否为可偏向状态

mark的偏向锁的锁标志位为 01


2.3 判断mark中JavaThread的状态


  • 若指向当前线程,则执行同步代码块
  • 若为空,则走4
  • 若指向其它线程,则走5

2.4 执行CAS原子指令

设置mark中JavaThread为当前线程ID。

若CAS成功,则执行同步代码块,否则走5。


2.5 执行CAS失败

说明当前存在多个线程竞争锁,当达到全局安全点(safepoint),获得偏向锁的线程就会被挂起,撤销偏向锁,并升级为轻量级锁。

升级完成后被阻塞在安全点的线程继续执行同步代码块。

BiasedLocking::Condition BiasedLocking::revoke_and_rebias(Handle obj, bool attempt_rebias, TRAPS) {assert(!SafepointSynchronize::is_at_safepoint(), "must not be called while at safepoint");// We can revoke the biases of anonymously-biased objects// efficiently enough that we should not cause these revocations to// update the heuristics because doing so may cause unwanted bulk// revocations (which are expensive) to occur.// step1markOop mark = obj->mark();if (mark->is_biased_anonymously() && !attempt_rebias) {// We are probably trying to revoke the bias of this object due to// an identity hash code computation. Try to revoke the bias// without a safepoint. This is possible if we can successfully// compare-and-exchange an unbiased header into the mark word of// the object, meaning that no other thread has raced to acquire// the bias of the object.markOop biased_value = mark;markOop unbiased_prototype = markOopDesc::prototype()->set_age(mark->age());markOop res_mark = (markOop) Atomic::cmpxchg_ptr(unbiased_prototype, obj->mark_addr(), mark);if (res_mark == biased_value) {return BIAS_REVOKED;}} else if (mark->has_bias_pattern()) {Klass* k = obj->klass();markOop prototype_header = k->prototype_header();if (!prototype_header->has_bias_pattern()) {// This object has a stale bias from before the bulk revocation// for this data type occurred. It's pointless to update the// heuristics at this point so simply update the header with a// CAS. If we fail this race, the object's bias has been revoked// by another thread so we simply return and let the caller deal// with it.markOop biased_value = mark;markOop res_mark = (markOop) Atomic::cmpxchg_ptr(prototype_header, obj->mark_addr(), mark);assert(!(*(obj->mark_addr()))->has_bias_pattern(), "even if we raced, should still be revoked");return BIAS_REVOKED;} else if (prototype_header->bias_epoch() != mark->bias_epoch()) {// The epoch of this biasing has expired indicating that the// object is effectively unbiased. Depending on whether we need// to rebias or revoke the bias of this object we can do it// efficiently enough with a CAS that we shouldn't update the// heuristics. This is normally done in the assembly code but we// can reach this point due to various points in the runtime// needing to revoke biases.if (attempt_rebias) {assert(THREAD->is_Java_thread(), "");markOop biased_value = mark;markOop rebiased_prototype = markOopDesc::encode((JavaThread*) THREAD, mark->age(), prototype_header->bias_epoch());markOop res_mark = (markOop) Atomic::cmpxchg_ptr(rebiased_prototype, obj->mark_addr(), mark);if (res_mark == biased_value) {return BIAS_REVOKED_AND_REBIASED;}} else {markOop biased_value = mark;markOop unbiased_prototype = markOopDesc::prototype()->set_age(mark->age());markOop res_mark = (markOop) Atomic::cmpxchg_ptr(unbiased_prototype, obj->mark_addr(), mark);if (res_mark == biased_value) {return BIAS_REVOKED;}}}}HeuristicsResult heuristics = update_heuristics(obj(), attempt_rebias);if (heuristics == HR_NOT_BIASED) {return NOT_BIASED;} else if (heuristics == HR_SINGLE_REVOKE) {Klass *k = obj->klass();markOop prototype_header = k->prototype_header();if (mark->biased_locker() == THREAD &&prototype_header->bias_epoch() == mark->bias_epoch()) {// A thread is trying to revoke the bias of an object biased// toward it, again likely due to an identity hash code// computation. We can again avoid a safepoint in this case// since we are only going to walk our own stack. There are no// races with revocations occurring in other threads because we// reach no safepoints in the revocation path.// Also check the epoch because even if threads match, another thread// can come in with a CAS to steal the bias of an object that has a// stale epoch.ResourceMark rm;if (TraceBiasedLocking) {tty->print_cr("Revoking bias by walking my own stack:");}EventBiasedLockSelfRevocation event;BiasedLocking::Condition cond = revoke_bias(obj(), false, false, (JavaThread*) THREAD, NULL);((JavaThread*) THREAD)->set_cached_monitor_info(NULL);assert(cond == BIAS_REVOKED, "why not?");if (event.should_commit()) {event.set_lockClass(k);event.commit();}return cond;} else {EventBiasedLockRevocation event;VM_RevokeBias revoke(&obj, (JavaThread*) THREAD);VMThread::execute(&revoke);if (event.should_commit() && (revoke.status_code() != NOT_BIASED)) {event.set_lockClass(k);// Subtract 1 to match the id of events committed inside the safepointevent.set_safepointId(SafepointSynchronize::safepoint_counter() - 1);event.set_previousOwner(revoke.biased_locker());event.commit();}return revoke.status_code();}}assert((heuristics == HR_BULK_REVOKE) ||(heuristics == HR_BULK_REBIAS), "?");EventBiasedLockClassRevocation event;VM_BulkRevokeBias bulk_revoke(&obj, (JavaThread*) THREAD,(heuristics == HR_BULK_REBIAS),attempt_rebias);VMThread::execute(&bulk_revoke);if (event.should_commit()) {event.set_revokedClass(obj->klass());event.set_disableBiasing((heuristics != HR_BULK_REBIAS));// Subtract 1 to match the id of events committed inside the safepointevent.set_safepointId(SafepointSynchronize::safepoint_counter() - 1);event.commit();}return bulk_revoke.status_code();
}

3 偏向锁的撤销

只有当其它线程尝试竞争偏向锁时,持有偏向锁的线程才会释放锁。

偏向锁的撤销由BiasedLocking::revoke_at_safepoint实现:

void BiasedLocking::revoke_at_safepoint(Handle h_obj) {assert(SafepointSynchronize::is_at_safepoint(), "must only be called at safepoint");oop obj = h_obj();HeuristicsResult heuristics = update_heuristics(obj, false);if (heuristics == HR_SINGLE_REVOKE) {revoke_bias(obj, false, false, NULL, NULL);} else if ((heuristics == HR_BULK_REBIAS) ||(heuristics == HR_BULK_REVOKE)) {bulk_revoke_or_rebias_at_safepoint(obj, (heuristics == HR_BULK_REBIAS), false, NULL);}clean_up_cached_monitor_info();
}

  1. 偏向锁的撤销动作必须等待全局安全点(safepoint,GC时会让所有线程阻塞的停顿点)
  2. 暂停拥有偏向锁的线程,判断锁对象是否处于被锁定状态
  3. 撤销偏向锁,恢复到无锁(标志位 01)或轻量级锁(标志位 00)状态

偏向锁在Java 1.6后默认启用,但在应用程序启动几s后才激活,可关闭延迟:

-XX:BiasedLockingStartupDelay=0

若确定应用程序中所有锁通常情况下处于竞争状态,可关闭偏向锁:

XX:-UseBiasedLocking=false(默认打开)

偏向锁的释放

遍历线程栈的所有Lock Record,把ObjectReference切断,即ObjectReference = null.

把ObjectReference置null,但锁对象的对象头的Mark Word还是没改变,依然偏向之前的线程,那还是没释放锁的嘛,的确是,线程退出临界区时候,并没有释放偏向锁,这么做是为 : 当再次需要获取锁时,只需要简单判断是否是重入,即可快速获取锁,而不用每次都CAS,这也是偏向锁在只有一个线程访问锁的情景下高效的核心。


总结


  • 当出现锁资源访问的时候,都会在当前线程栈生成一条Lock Record,并且ObjectReference将指向锁对象的对象头 的Mark Word,该设置可能出现多线程,需CAS操作
  • 多线程情况下竞争同一个锁资源,偏向锁的撤销会影响效率
  • 偏向锁的重入计数依靠线程栈里Lock Record个数
  • 偏向锁撤销失败,最终会升级为轻量级锁
  • 偏向锁退出时并没有修改Mark Word,也就是没有释放锁
  • 偏向锁相对轻量级锁来说,当同一线程去再次获取锁的时候,不用进行CAS操作,提高了性能.(轻量级锁在同一线程情况下每次去获取锁,在无锁的状态下,每次都要进行一次CAS操作)
  • 偏向锁只有遇到其他线程尝试竞争偏向锁时,持有偏向锁的线程才会释放锁,线程不会主动去释放偏向锁
  • 偏向锁的撤销是很复杂,成为理解代码的障碍,也阻碍了对同步系统重构,而且现如今基本都是多核系统,偏向锁的劣势越来越明显,所以在Java 15废弃了偏向锁

推荐阅读
  • 带添加按钮的GridView,item的删除事件
    先上图片效果;gridView无数据时显示添加按钮,有数据时,第一格显示添加按钮,后面显示数据:布局文件:addr_manage.xml<?xmlve ... [详细]
  • vue使用
    关键词: ... [详细]
  • Iamtryingtomakeaclassthatwillreadatextfileofnamesintoanarray,thenreturnthatarra ... [详细]
  • android listview OnItemClickListener失效原因
    最近在做listview时发现OnItemClickListener失效的问题,经过查找发现是因为button的原因。不仅listitem中存在button会影响OnItemClickListener事件的失效,还会导致单击后listview每个item的背景改变,使得item中的所有有关焦点的事件都失效。本文给出了一个范例来说明这种情况,并提供了解决方法。 ... [详细]
  • Python正则表达式学习记录及常用方法
    本文记录了学习Python正则表达式的过程,介绍了re模块的常用方法re.search,并解释了rawstring的作用。正则表达式是一种方便检查字符串匹配模式的工具,通过本文的学习可以掌握Python中使用正则表达式的基本方法。 ... [详细]
  • C++字符字符串处理及字符集编码方案
    本文介绍了C++中字符字符串处理的问题,并详细解释了字符集编码方案,包括UNICODE、Windows apps采用的UTF-16编码、ASCII、SBCS和DBCS编码方案。同时说明了ANSI C标准和Windows中的字符/字符串数据类型实现。文章还提到了在编译时需要定义UNICODE宏以支持unicode编码,否则将使用windows code page编译。最后,给出了相关的头文件和数据类型定义。 ... [详细]
  • Android开发实现的计时器功能示例
    本文分享了Android开发实现的计时器功能示例,包括效果图、布局和按钮的使用。通过使用Chronometer控件,可以实现计时器功能。该示例适用于Android平台,供开发者参考。 ... [详细]
  • 怎么在PHP项目中实现一个HTTP断点续传功能发布时间:2021-01-1916:26:06来源:亿速云阅读:96作者:Le ... [详细]
  • 本文由编程笔记小编整理,主要介绍了使用Junit和黄瓜进行自动化测试中步骤缺失的问题。文章首先介绍了使用cucumber和Junit创建Runner类的代码,然后详细说明了黄瓜功能中的步骤和Steps类的实现。本文对于需要使用Junit和黄瓜进行自动化测试的开发者具有一定的参考价值。摘要长度:187字。 ... [详细]
  • 欢乐的票圈重构之旅——RecyclerView的头尾布局增加
    项目重构的Git地址:https:github.comrazerdpFriendCircletreemain-dev项目同步更新的文集:http:www.jianshu.comno ... [详细]
  • Android系统源码分析Zygote和SystemServer启动过程详解
    本文详细解析了Android系统源码中Zygote和SystemServer的启动过程。首先介绍了系统framework层启动的内容,帮助理解四大组件的启动和管理过程。接着介绍了AMS、PMS等系统服务的作用和调用方式。然后详细分析了Zygote的启动过程,解释了Zygote在Android启动过程中的决定作用。最后通过时序图展示了整个过程。 ... [详细]
  • 使用圣杯布局模式实现网站首页的内容布局
    本文介绍了使用圣杯布局模式实现网站首页的内容布局的方法,包括HTML部分代码和实例。同时还提供了公司新闻、最新产品、关于我们、联系我们等页面的布局示例。商品展示区包括了车里子和农家生态土鸡蛋等产品的价格信息。 ... [详细]
  • ShiftLeft:将静态防护与运行时防护结合的持续性安全防护解决方案
    ShiftLeft公司是一家致力于将应用的静态防护和运行时防护与应用开发自动化工作流相结合以提升软件开发生命周期中的安全性的公司。传统的安全防护方式存在误报率高、人工成本高、耗时长等问题,而ShiftLeft提供的持续性安全防护解决方案能够解决这些问题。通过将下一代静态代码分析与应用开发自动化工作流中涉及的安全工具相结合,ShiftLeft帮助企业实现DevSecOps的安全部分,提供高效、准确的安全能力。 ... [详细]
  • node.jsurlsearchparamsAPI哎哎哎 ... [详细]
  • php缓存ri,浅析ThinkPHP缓存之快速缓存(F方法)和动态缓存(S方法)(日常整理)
    thinkPHP的F方法只能用于缓存简单数据类型,不支持有效期和缓存对象。S()缓存方法支持有效期,又称动态缓存方法。本文是小编日常整理有关thinkp ... [详细]
author-avatar
woorain_77b002
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有