Hotspot 偏向锁的评估与改进(五)

154 阅读3分钟

第三章

3.3 膨胀

当某一线程尝试获取另外线程持有的轻量级锁时(CAS失败),非持有线程会尝试膨胀该锁. 膨胀期间,会为该锁分配并初始化重量级的对象监视器结构. 该线程使用循环来不断尝试,直到由自己或其他线程将锁成功膨胀. 当调用waitnotify时也会发生膨胀, 即使对象未被锁定. [11]

译者注 [11]: 对这句话不太理解,因为通常来说调用waitnodify时必须放到synchronized执行体内,而当线程执行到synchronized方法或块时肯定要先锁定对象啊,怎么可能出现上面出现的情况呢?

为了膨胀一个轻量级锁,膨胀线程首先使用CAS来将mark word设置为INFLATING(0),然后读取displaced mark word并将其拷贝到新建的监视器结构中. 其他线程遇到INFLACTING状态的mark word时会等待膨胀线程完成膨胀操作. 这其中也包括当前持有锁的线程,它也要等到膨胀完成后才能释放锁. 这个临时状态是必要的,否则膨胀线程可能读到过期的mark word版本[12]. 通过将锁设置为正在膨胀状态, 膨胀线程确保无法改变displaced mark word,进而安全地读取和拷贝.

译者注 [12]: 膨胀线程跟轻量级锁的持有线程是同一个线程吗? 如果不是的话它怎么能够访问到displace mark word呢? 因为该word存储在锁记录中,而该记录存储在线程栈中, 线程不能访问到其他线程的栈空间吧

以下动作取决于膨胀循环每次迭代时的锁状态:

  • 锁已膨胀(tag 10) 其他线程成功膨胀了该锁. 退出循环
  • 锁正在偏向(mark wordINFLATING) 其他线程正在偏向,等待直到偏向完成
  • 当前是轻量级锁(tag 00) 分配对象监视器, 然后使用CAS将mark word设置为INFLATING. 如果CAS失败,回收监视器并重新进入膨胀循环. 如果CAS成功,通过建立适当的字段来安装监视器,最后将监视器指针存储到mark word(覆盖INFLATING)
  • 锁已释放(tag 01) 分配并建立对象监视器, 然后使用CAS将监视器对象的引用存储到mark word中. 如果失败, 回收监视器并重新进入膨胀循环, 否则跳出循环.

一旦锁膨胀,其他正在尝试获取的线程可简单地使用底层的监视器机制来获取锁. 可在图3.4中查看重量级锁. 在STW期间,Hotspot回收(deflates)空闲的监视器从而允许不再竞争的锁再次回到轻量级或偏向锁. 这是非常安全的因为STW期间没有线程可以获取或释放锁.

图3.4 是重量级锁示例