synchronized升级流程,基于c++整理
2022-06-02 18:02:00 7 举报
synchronized升级流程,超详细带总结
作者其他创作
大纲/内容
偏向锁重入
ObjectSynchronizer
// 把本线程添加进重量级锁中enter(THREAD)
类型一致,纪元一致,只能是线程不一致
升级为轻量级锁结束
匿名偏向或偏向其他线程
发生了批量偏向锁撤销
进入重量级锁阻塞队列,等待获取锁,详情省略
线程id epoch 及锁 类型一致
发生了批量重偏向
// 新建一个对象头,复制于原锁对象头,但末尾设置为01,代表无锁状态; markOop displaced = lockee->mark()->set_unlocked(); // 把新对象头存入BasicObjectLock(即lockRecord) entry->lock()->set_displaced_header(displaced);
本线程原为轻量级锁持有者,设置为重量级锁持有者后返回
是匿名偏向?即锁对象头无其他线程id?
当前轻量级锁,则新建并设置objectMonitor对象,把轻量级锁指向的锁记录(lock_record)设置为重量级锁持有者,而后返回
轻量级锁重入结束
获得重量级锁结束
// 是否是持有锁的线程THREAD->is_lock_owned((address)mark->locker())
// 偏向锁批量撤销BiasedLocking::revoke_at_safepoint(obj)
N
当前线程是否是锁的持有线程 THREAD->is_lock_owned((address) displaced->clear_lock_bits())
Y
if (cur == NULL)
锁对象允许使用偏向锁if (mark->has_bias_pattern())
重偏向成功
循环直至获得重量级锁
epoch(纪元)不一致
if (mark-has_monitor())
CAS成功,即第一个获得锁,直接返回
锁类型不一致,对象头非偏向锁
偏向本线程结束
已经是重量级锁,说明有其他竞争线程已经执行了膨胀,直接返回
此处开始将不再有偏向锁
// 处理偏向锁的 批量重偏向 或 撤销// 因为可能发生了偏向锁撤销// 当某个类的实例对象作为锁对象时,// 这些对象每发生一次锁撤销,都会更新// 撤销次数,当次数达到某个阈值时,// 触发span style=\"font-size: inherit;\
if (cur == Self)
锁升级流程的要点:1.基本流程遵循可偏向 ->偏向锁->轻量级->重量级的顺序。需要注意的是偏向锁在jdk9出现,后被迁移至jdk8u中,并默认开启;后又因逻辑复杂维护困难在jdk15中被取消默认,需要显式开启2.lock_record即锁记录不仅仅存在于轻量级锁状态下,而是只要尝试获取锁都会生成,针对已经持有锁的线程,每次重入都会增加一条;lock_record存在于线程栈中,每一个lock_record都有一个指向锁对象的指针,当锁发生膨胀后,如果锁当前被某个线程持有,那么就会遍历这个线程的栈,找到所有的lock_record记录,将他更新成符合当前锁状况的样式3.偏向锁撤销当偏向锁在发生线程竞争时,此处的竞争指的是当一个线程A持有锁后,另一个线程B也试图来获取锁。注意,无论此时A是否还在同步代码块中,甚至A线程已经关闭,当B线程来时,仍被认为发生竞争,进行偏向锁撤销。(如2所述,如果此时A线程仍在执行同步代码块内容,则B线程会去遍历A线程的栈更新lock_record,使其符合轻量级锁的样式)4.批量重偏向每次偏向锁竞争,都执行偏向锁撤销->升级轻量级锁,这一过程消耗较大,尤其是对于两个线程轮流持锁的场景,根本无需锁膨胀。因此提出优化:偏向锁的撤销将会被计数,计数是针对锁对象所属的class的,比如某class有几十个对象实例,这些实例都被用作锁来管理同步代码块,初始大家都能进入偏向锁,但随着线程竞争,一定数量的(-XX:BiasedLockingBulkRebiasThreshold 默认20)的偏向锁被撤销后,将触发批量重偏向。即会去更新该class类对象以及其实例对象(锁对象)的对象头(更新仅限于正在被线程持有的锁),这样那些没被线程持有的锁就落后了,当他们后续被线程X尝试获取时,发现锁状态落后,说明此时他们的偏向锁并没被持有,就可以直接偏向X并更新锁状态,而不用进行偏向锁撤销。可以看到,这种优化是为了解决偏向锁只有进入却没有退出的弊端,降低了不必要的偏向锁撤销及锁膨胀。但他需要去找到活跃线程,更新对应锁对象头,也额外增加了消耗。5.批量偏向撤销如4批量重偏向,这是另一个撤销计数阈值(-XX:BiasedLockingBulkRevokeThreshold 默认40);当针对某class发生批量重偏向后,如果短时间内(-XX:BiasedLockingDecayTime 默认25秒)该class的实例对象(锁对象)仍有大量偏向锁被撤销,总数达到40次(即偏向后又被撤销了40-20=20个偏向锁),说明存在激烈竞争,此时就认定无需再使用偏向锁了。即会去更新该class类对象的锁状态至无锁态,后续线程尝试获取该calss下所有锁时,都将直接走轻量级锁逻辑6.轻量级锁当一个线程持有偏向锁,并仍在运行同步代码块时,另一个线程来获取锁,那么锁将很快膨胀至重量级。即轻量级锁转瞬即逝。仅有当一个线程退出了偏向锁的持有,再由另一个线程来获取,才会较为长久的运行在轻量级锁的状态(事实上,这种场景不必升级至轻量级锁,这也就是4.批量重偏向引入的目的)7.自旋轻量级锁升级时没有自旋,甚至没有CAS(网传的CAS其实发生在无锁状态竞争),仅简单的判断下本线程是否是轻量级锁的持有者(此时锁对象头指向持有者线程的lock_record),如果是,则做一个重入,如果不是,则走锁膨胀逻辑;自旋发生在已膨胀至重量级锁后,线程试图获取该重量级锁,但如果阻塞进入等待队列太耗时,因此会进行一定次数的自旋,该自旋具有自适应性,有自旋次数和消耗时间的约束——BiliBili : 吃菠菜的太守
// 重量级锁时该值没有用,//随便为displaced_mark_word设置个什么值,但不能为null// 以防止和锁重入标记混淆lock->set_displaced_header(markOopDesc::unused_mark());
if (mark == markOopDesc::INFLATING())
其他场景
进行一定次数的自旋,进行CAS,成功后返回
//为lockRecords置空displaced_mark_word// 即记作一次轻量级锁的重入lock->set_displaced_header(NULL)
// 依据jvm参数是否开启偏向//-XX: +UseBiasedLockingif (UseBiasedLocking)
N : 偏向锁发生竞争
方法详情
当前无锁,则新建并设置objectMonitor对象,而后返回
if (Self-is_lock_owned ((address)cur))
N:CAS失败
非安全点,本场景都走该分支
原持有者就是自己,锁重入,计数++
Y:与锁对应的类的对象头对比
InterpreterRuntime
正在膨胀中,说明有其他竞争线程正在执行膨胀,跳过本次循环
// 是否无锁状态,只有匿名偏向状态//才可以使用偏向锁,偏向锁撤销后成为//无锁状态,无锁状态必须CAS升级if (mark->is_neutral())
//轻量级锁的重入,lockRecord//不需要Display_mark_word entry->lock()->set_displaced_header(NULL)
if (mark-has_locker())
if (Knob_SpinEarly && TrySpin (Self) 0)
//为lockRecords设置displaced_mark_word// 即无锁升级为轻量级锁lock->set_displaced_header(mark)
安全点:所有普通线程停止意味着当前线程为虚拟机线程所以此时无法偏向当前线程
偏向锁被撤销后,无法再使用偏向锁,准备建立轻量级锁
重偏向本线程成功结束
N:有锁
byteCodeInterPreter
CASE(_monitorenter) {//在栈中找到一个空闲的LockRecord//为它设置一个目标锁对象 entry->set_obj(lockee);}
收藏
0 条评论
下一页