前言
对于 Hotpot JVM 中的偏差锁,大部分开发者都比拟相熟或者至多据说过。那咱们用上面 10 个对于偏差锁的进阶问题,测验一下本人离精通还有多远。
- 如何判断以后锁对象为偏差锁
- 偏差锁如何判断锁重入
- 当代码运行至 synchronized 润饰的代码块时,合乎什么条件才会尝试获取偏差锁
- 线程进入偏差锁后,会不会创立 lock record
- 偏差锁收缩后,lock record 有什么变动
- 如何判断以后持有锁的线程曾经因为批量重偏差,而被撤销了偏差锁
- 批量撤销和批量重偏差的触发条件是什么
- 批量重偏差后,lock record 和锁对象有什么变动
- 批量撤销后,lock record 和锁对象有什么变动
- 批量撤销 / 重偏差后,新创建的锁对象,是否反对偏差锁
看了下面的问题,如果是胸有成竹,那就能够跳过这篇文章了。如果一脸问号,这篇文章应该对你有所帮忙。
名词解释
首先明确下文章中用到的名词,因为不同人可能叫法不一样。
对象头,Java 对象在堆中存储时,会依照对象头加实例数据的构造来存储。这篇文章只讲锁,所以个别是指对象头中的 Markword 局部。
klass 对象 ,jvm 在加载类之后,会在堆内存中生成该类的对象,就是咱们代码中 this.getClass() 获取的对象。
锁对象,synchronized 指定的锁对象。对于一般办法,这个对象默认是 this 指针。对于静态方法,锁对象是堆里的 class 对象。
Lock record,进入 synchronized 时在线程栈中生成的锁记录,对这个不相熟的能够百度一下或看一下《深刻 java 虚拟机》这本书
锁收缩,hotspot 中从轻量级锁升级成重量级锁称之为收缩,为了便于了解,通常把偏差锁升级成轻量级锁也称为收缩。
问题解析
问题 1:如何判断以后锁对象为偏差锁
这个问题比较简单,个别理解过对象头或者偏差锁的都比拟相熟。当锁对象为偏差锁时,Markword 的偏差锁标识位为 1,锁标识位为 01。即 markword 的最初 3 位为 101。
问题 2:偏差锁如何判断锁重入
接下面问题的 Markword 构造,当曾经有线程获取到偏差锁,它的 id 就会填到 markword 中的线程 id 中。重入时线程只有查看 thread id 里存的是否就是本人线程的 id 就能够了。
问题 3:合乎什么条件才会尝试获取偏差锁
首先,hotspot 中通过参数 UseBiasedLocking 管制是否启用偏差锁,不设置时默认是启用的。如果想要禁用偏差锁,能够在启动参数中增加 -XX:-UseBiasedLocking。
是不是这样答复这个问题就完结了呢?答案是否定的。hotspot 还有一个提早偏差的概念,就是在 jvm 启动的时候是有一个延迟时间,过了这段时间后偏差锁才开始启用。这个延迟时间通过启动参数 BiasedLockingStartupDelay 来设置,默认为 4 秒。那提早的目标是什么呢?hotspot 的解释是在 jvm 启动过程中,外部有多个逻辑会用到锁,比方类加载。如果一开始就启用偏差锁,就导致频繁的撤销偏差锁,偏差锁的撤销须要在平安点执行,这样有可能影响 jvm 启动的速度。
满足下面 2 个条件之后,是不是就欢快的进入偏差锁了呢,其实还要通过 2 关。
第三个条件就是锁对象没有收缩,如果锁对象曾经收缩成轻量级锁了,那就不会再走偏差锁了。这就是常常说的锁只反对降级,不反对降级。轻量级锁的 markword 如下:
最初,如果锁对象对应的 class 产生了批量撤销的动作,也不会再进入偏差锁了。比方有 10 个锁对象 lockobj0..lockobj9,他们都是 LockObj 类的实例,如果产生偏差锁的批量撤销,那在这 10 个锁对象上的抢锁操作都不会再走偏差锁逻辑。
问题 4:线程进入偏差锁后,会不会创立 lock record
理解轻量级锁逻辑的都晓得,轻量级锁加锁后,锁对象会保留 lock record 的援用,关系如下:
那偏差锁有没有呢?答案是有的。其实轻量级锁的这个 lock record 在运行至 synchronized 的时候就创立了,这个时候 jvm 还不晓得具体应用的是偏差锁还是轻量级锁,偏差锁和轻量级锁用的是同一个 lock record。偏差锁的时候,对象头里没有 lock record 的指针。
然而,咱们再深挖一层,是不是每次都会创立?答案是否定的。比方在同一个办法中,对同一个锁对象的重入,就不会再次创立 lock record,比方上面的代码(尽管不会有人这么写代码😄):
public void testSync() {synchronized (this) {
//first time
synchronized (this) {// second time}
}
}
问题 5:偏差锁收缩后,lock record 有什么变动
首先,来看下收缩前的 lock record 和锁对象,它们的关系如下:
栈中的 lock record 蕴含了指向锁对象的指针和 markword 的正本。
锁收缩后可能呈现两种状况:
1)抢锁线程取得了轻量级锁,则替换 lock record 中的 displace_header 的锁状态位为无锁。
2)如果是轻量级锁的锁重入,则会降 lock record 的 displace_header 设置为空
3)其它线程持有轻量级锁,则会收缩成重量级锁,这时候 lock record 曾经没用了,会将将 markword 锁标记为设置为 011,代表曾经不应用了
问题 6:如何判断持有锁的线程曾经因批量重偏差被撤销
当产生批量重偏差时,jvm 会将 klass 对象的 markword.epoch+1。并且遍历所有该类型的锁对象,如果加锁的线程依然存活,则也会将锁对象的 epoch 设置成跟 klass 一样。
所以,如果另外一个线程在进入偏差锁逻辑时,发下锁对象的 epoch 跟 klass 的 epoch 不相等,则能够必定该偏差锁曾经被撤销。
问题 7:批量撤销和批量重偏差的触发条件是什么
jvm 通过两个参数来管制何时触发批量重偏差和批量撤销。
- BiasedLockingBulkRebiasThreshold,批量偏差阈值,默认值 20。
- BiasedLockingBulkRevokeThreshold,批量撤销阈值,默认值 40。
当同一类型的锁对象上产生锁争抢累计达到这两个数字时就会触发批量重定向和批量撤销。
划重点,这两个累计值是在 klass 对象上,不是锁对象上。
问题 8:批量重偏差后,lock record 和锁对象有什么变动
能够参考问题 6,批量重偏差后,klass 对象和依然活着的线程持有的锁对象,epoch 会加 1。也就是说,以后线程抢的偏差锁的持有线程如果挂了,那 epoch 不会变,就会被抢锁线程撤销或重偏差到以后线程。
问题 9:批量撤销后,lock record 和锁对象有什么变动
批量撤销后,klass 和所有雷同锁对象的偏差锁都会被撤销,markword 的锁标识位变成无锁。
问题 10:批量撤销 / 重偏差后,新创建的锁对象,是否反对偏差锁
- 批量重偏差后,新创建的锁对象,默认依然是偏差锁。
- 批量撤销后,新创建的锁对象,默认都会是轻量级锁(无锁)。因为产生批量撤销后,klass 对象的 markword 锁标识位变成无锁,所以在这之后创立的锁对象,默认跟 klass 对象的 markword 雷同。
总结
jvm 因为退出了偏差锁逻辑而大大提高了同步锁的速度。然而偏差锁不是万能的,尤其是当初互联网利用并发越来越高,偏差锁在过多的争抢下反而会影响效率并且很快就会产生收缩,曾经越来越偏离了了它设计时的初衷。以后的 Java 利用中也根本会应用 JUC 包来做并发的同步,偏差锁的应用场景越来越少。当然硬件性能的晋升也在减弱偏差锁的劣势,所以 Java15 默认敞开了偏差锁。当然,本篇文章对于你加入面试还是可能提供一点点帮忙的。
原文:jianshu.com/p/a76a6c6d68e1
近期热文举荐:
1.1,000+ 道 Java 面试题及答案整顿(2022 最新版)
2. 劲爆!Java 协程要来了。。。
3.Spring Boot 2.x 教程,太全了!
4. 别再写满屏的爆爆爆炸类了,试试装璜器模式,这才是优雅的形式!!
5.《Java 开发手册(嵩山版)》最新公布,速速下载!
感觉不错,别忘了顺手点赞 + 转发哦!