死磕synchronized五:系统剖析轻量级锁

哈喽,大家好,我是江湖人送外号[道格牙]的子牙老师。

近期准备写一个专栏:从Hotspot源码角度剖析synchronized。前前后后大概有10篇,会全网发,写完后整理成电子书放公众号供大家下载。对本专栏感兴趣的、希望彻彻底底学明白synchronized的小伙伴可以关注一波。电子书整理好了会通过公众号群发告知大家。我的公众号:硬核子牙

本篇文章给大家分享关于轻量级锁的所有。在synchronized的所有锁类型中,轻量级锁是最简单的,但是这个简单是相对的,如果不具备一些底层的思维及学习底层的办法,它的源码也不是那么容易理解。

关于轻量级锁

首先说下什么情况会用到轻量级锁

  1. 延迟偏向期间,偏向锁还不可用,synchronized首先拿到的就是轻量级锁。即由无锁升级成轻量级锁
  2. 抢占偏向锁失败的线程会触发锁膨胀至轻量级锁。这里还需要考虑偏向锁重入的情况
  3. 膨胀成轻量级锁以后,如果后续的线程是在持有锁的线程执行结束后来抢锁,拿到的依然是轻量级锁。因为释放轻量级锁会恢复成无锁
  4. 膨胀成轻量级锁以后,如果是来抢锁,就会触发膨胀成重量级锁

再说下轻量级锁在对象头中的存在形式,如图。

针对大家的这么几个疑惑说明一下:1、64位操作系统,内存地址是8字节,64位,为什么能用62位保存?因为内存地址虽然可以用64位,实际上只用了48位,还有16位是保留位,所以完全可以;2、为什么可以用尾两位的00表示轻量级锁?言外之意就是说内存地址的尾两位一定是00吗?是的。64位操作系统,线程栈是按8字节为一个单位来用的,所以尾两位一定是00。

很多小伙伴还有一个疑惑,轻量级锁对象到底存放在虚拟机栈的什么位置?从这张图也可以看出来,是存放在栈帧的顶部。如果该方法被synchronized修饰,或者方法中有synchronized代码段,就会在栈帧的顶部创建一块叫monitor block区域,专门用来存放lock record。

接下来看下Hotspot源码是如何实现轻量级锁的、如何处理轻量级锁的重入、如何处理偏向锁膨胀成轻量级锁的兼容。

轻量级锁前的堆栈图

如果是synchronized修饰的方法,会先执行lock_method方法。这个方法做了三件事情:

  1. 根据是否是静态方法计算出锁对象,将锁对象的内存地址保持到rax寄存器中
  2. 在当前的栈帧中创建一个lock record,将锁对象的内存地址写入
  3. 调用lock_object完成加锁

lock_method执行之后的堆栈图

lock_object

这个方法是模板解释器处轻量级锁的抢锁与重入处理逻辑。我们Java代码中的synchronized的轻量级锁都是在这里处理的。

这个方法全是汇编,我就不贴源码了,以伪代码的方式直接讲它的执行逻辑

image.png

这个方法执行后,如果不发生重入,堆栈图还是上面那个,不会变,只是将lock record的内存地址通过CAS写入到对象头中。

如果是重入,CAS是失败的,对象头不会发生变化,堆栈图会发生变化:

  1. 如果是synchronized修饰方法这种情况,一个栈帧一个lock record,区别是第一个BasicLock.display_header不为0,其他都为0。为什么要这样呢?为了释放锁的时候识别哪个是最后一层
  2. 如果是synchronized代码块,一个栈帧中就有多个lock record,最后一层的BasicLock.display_header不为0,其他都为0。如图

轻量级锁解锁流程

轻量级锁的解锁逻辑比较简单:

  1. 判断是不是重入,如果是重入,直接return;
  2. 如果不是重入,意味着在解最后一层轻量级锁,需要将对象头恢复成无锁,这样后面的线程才能拿到轻量级锁。这个无锁头从哪来的?回忆一下上锁的时候,把rax寄存器中的无锁头写入了BasicLock.display_header,就是从这拿的;
  3. 其他情况就按重量级锁解锁处理。

image.png

偏向锁膨胀与重入

如果是偏向锁膨胀成轻量级锁,或者是重入的偏向锁膨胀成轻量级锁,Hotspot是如何处理的呢?直接看源码。注释写的很详细,就不重复啰嗦了。

image.png

至此就把轻量级锁相关的知识点讲完了。

系列文章

1、JVM如何执行synchronized修饰的方法

2、死磕synchronized二:系统剖析延迟偏向篇一

3、死磕synchronized三:系统剖析延迟偏向篇二

4、死磕synchronized四:系统剖析偏向锁篇一

推荐阅读

1、今天聊点不一样的,百万年薪需要具备的能力

2、你是不是想问,那些技术大牛是如何练成的?我来告诉你

3、深入剖析Lambda表达式的底层实现原理

结语

结语

其实技术这个行业真的不难,如果有人带,打底子1-2年,沉淀2-3年,足矣。我自己一步步探索,走得还算顺利,大概花了七年时间。

给大家看看我写好的

image.png

image.png

image.png

猜你喜欢

转载自juejin.im/post/7034695606299787295
今日推荐