Eliminating Synchronization-Related Atomic Operations with Biased Locking and Bulk Rebiasing 读书笔记

本贴最后更新于 3032 天前,其中的信息可能已经时移世改


Eliminating Synchronization-Related Atomic Operations with Biased Locking and Bulk Rebiasing 读书笔记

之所以来看这英文原文,是因为发现在我目前看得两本对于偏向锁及轻量级锁有描述的书都感觉说的不清楚,让人感觉云里雾里,甚至有谬误。

果然,还是到技术的源头寻找答案更靠谱。

原文链接

论文主要讲述了以下部分的内容:

  • Hotspot中轻量级锁的实现
  • 偏向锁的最基础的版本
  • 用于减少偏向锁 重偏向 及 撤销 损耗的 批量重偏向 批量撤销技术-bulk rebiasing
  • 使用epoch提高 bulk-rebasing 技术在 堆容量较大时的实用性

HotSpot中轻量级锁的实现

无论轻量级锁还是偏向锁技术的实现都基于以下几个假定:

  • 加锁执行的部分是结构化的。(可以理解为,所有的加锁操作与解锁操作字节码都是在同一个方法内配对出现)
  • JIT编译只编译 结构化加锁 的方法
  • 非结构化加锁的方法必须在解析阶段就被识别出来

轻量级锁加锁过程

在HOTSPOT的实现里,当在方法对对象OBJ执行monitorenter这个字节码的时候,将会将 OBJ的Markwrod拷贝到栈中的当前方法对应的栈帧中。这个拷贝的Markword被称为 displace markwrod,也被称为lock record.然后采用CAS对比 栈中的markword以及OBJ中的markword

  • 如果相同,则CAS成功,将OBJ的markword变成轻量锁状态,并将markword里预留的指针指向lock record;
  • 如果CAS失败,则说明有高冲突,将会将当前轻量级锁升级为重量级锁:请求锁CAS失败的线程将会通过CAS替换OBJ的Markword为重量级锁,且Markword里的指针指向 引入的 mutex以及condition变量。

轻量级锁解锁过程

在轻量级锁解锁过程时,将会用CAS比较OBJ中存放的Markword是否依然指向栈帧中的Lock Record:

  • 如果是,则OBJ的Markword将会被替换为LockRecord中的MarkWord,并在栈帧中删除对应的Lock Record,解锁完毕
  • 如果不是,则说明锁已经升级成了重量级锁,此时,释放锁的线程将释放重量级锁对应的mutex,以及通知其余在等待队列中的线程,让其开始竞争。

可重入锁的加锁与解锁

当一个线程已经获得了某个对象的轻量级锁,当其重入对应的锁时,只需要在栈帧中加入一个未初始化(值为0)的LockRecord记录即可。

解锁时,判断若LockRecod的值为0,则知道,这是一个重入的锁,仅仅在队列中移除一条LockRecord的记录即可。

这些LockReocrd可以表明当前方法重入了多少次。

栈帧与LockRecord

无论是解析执行还是编译执行,每个方法都会有一个对应的栈帧。解析执行时,一个栈帧对应一个JAVA方法。但编译执行时,一个栈帧可能由于内联优化,会对应多个方法。

解析状态的栈帧会有一个区域存放LockRecord,这个区域的大小会随着持有的LockRecord的增删变化而变化。

编译状态时,LockRecord不保存在栈帧中,而是保存在所谓的 register spill stack slots中。编译时会计算并保存 在每个线程安全点时 持有的锁 以及 对应lockRecord 对应的位置等元信息。

LockRecord信息的记录使得系统可以在运行时获得锁的记录信息,这些信息对 偏向锁的撤销也有作用

偏向锁的基本实现

当一个对象刚刚被创建,且其所属的类 被JVM推断为适合 进行偏向锁时,其markword会被标志为,可偏向且未偏向。

偏向锁申请

当一个线程向一个 可偏向但未偏向的的 对象申请锁时,会通过CAS比较Markword是否已经变更:

  • 若未变更,则线程成功获得该偏向锁,并在OBJ的Markword里写上获得偏向锁的线程
  • 若已经变更,则获取锁失败的线程需要撤销偏向锁。这需要等到全局安全点时才可以执行。线程需要遍历并修改持有 偏向锁的线程的栈,为其未初始化的RecordLock补齐相关displace Markword的信息,使得其看起来就像一开始就在轻量级锁的环境中运作一样,最后还要修改OBJ的MarkWord使其变成轻量锁模式;但若在遍历锁对象拥有线程的栈时,发现其当前并没有持有锁的话,将会将对象恢复到 可偏向,但未偏向的状态。然后继续通过CAS竞争偏向锁。

如果一个线程已经获得了偏向锁,那么线程后续进入同步快的时候,只需要校验一下OBJ的Markword指向的线程是否与本线程相同即可。存在栈帧里的LockRecord也并没有被初始化,因为这一直都不需要使用。

如果对象可偏向且已偏向,但其他线程也要获取锁时,将会走跟上面CAS失败一样的过程。

偏向锁释放

当释放偏向锁的时候,需要判断一下当前是否还处于偏向锁状态,

  • 如果是,则简单的移除LockRecord记录即可,连当前偏向锁是否指向本线程都无需判断;
  • 如果不是,则走轻量级锁的锁释放流程,相关需要的信息都由其他申请锁的线程准备好了

批量偏向锁重偏向及撤销

这一节没看太懂,可能以下理解有错误,估计意思是

有一些对象锁并不能从偏向锁中获得效率的提升,这些对象需要被识别,并一开始就设置为非偏向模式

有一些对象初始化时,需要由创建线程进行同步初始化,后续的操作都变成另外一个单独的线程单独同步处理,对于这些对象,需要用到bulk rebiasing的技术

使用EPOCH提高bulk biasing的效率

没看懂,后续再补吧

相关帖子

欢迎来到这里!

我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。

注册 关于
请输入回帖内容 ...