顾
正文:
如何判断当前锁对象为偏向锁
偏向锁如何判断锁重入
当代码运行至synchronized修饰的代码块时,符合什么条件才会尝试获取偏向锁
线程进入偏向锁后,会不会创建lock record
偏向锁膨胀后,lock record有什么变化
如何判断当前持有锁的线程已经因为批量重偏向,而被撤销了偏向锁
批量撤销和批量重偏向的触发条件是什么
批量重偏向后,lock record和锁对象有什么变化
批量撤销后,lock record和锁对象有什么变化
批量撤销/重偏向后,新创建的锁对象,是否支持偏向锁
看了上面的问题,如果是胸有成竹,那就可以跳过这篇文章了。如果一脸问号,这篇文章应该对你有所帮助。
问题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相同。
END
往期精彩1.select......for update会锁表还是锁行?2.SpringBoot Seata 死锁问题排查3.MySQL innoDB 间隙锁产生的死锁问题4.并发环境下如何利用加锁机制保证数据一致性我是技术管家,专心做内容,不割韭菜
分享技术成长之路,不忘初心,惠泽他人终身学习,与时俱进,点赞关注不迷路