前言

最近看了一些synchronized底层原理的博文,对偏向锁这块儿不是很理解,结合网上一些博主的文章和示例代码,记录下自己的理解。

参考的文章有:
1.程序员囧辉的全网最硬核的 synchronized 面试题深度解析
2.Fisher3652的并发编程:批量重偏向、批量撤销

相关概念

锁升级流程:

对象头组成:

小端模式表示对象头

小端模式:Inter x86、ARM核 采用的是小端模式存储数据(低地址存放低字节数据,高地址存放高字节数据)

 OFFSET  SIZE               TYPE DESCRIPTION                               VALUE0     4                    (object header)                           05 01 00 00 (00000101 00000001 00000000 00000000) (261)4     4                    (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)8     4                    (object header)                           ca d8 00 f8 (11001010 11011000 00000000 11111000) (-134162230)12     4   java.lang.String User.name                                 null

以上对象头中Mark word 4个字节的数据,是按照内存地址由低到高打印的,而内存中的地址是向上增长的,低地址存放低字节数据,高地址存放高字节数据,所以,Mark word实际的数据以16进制表示为:0x00000105

这样便与上图中对象头的 Mark word 的32位数据对应了起来,即锁标志位在低位。

启动偏向锁设置

JVM为我们提供了参数- XX:+UseBiasedLocking以开启或者关闭偏向锁优化(默认开启),但jvm延迟了4s启动偏向锁,想要在程序已启动时,就启动偏向锁,有两种方式:

  • 程序启动时,让主线程睡眠超过4s,这样就会在jvm延迟启动偏向锁后,才开始运行程序。
  • 设置jvm启动参数,添加-XX:BiasedLockingStartupDelay=0,即设置延迟启动偏向锁的时间为0s,这样jvm启动时,偏向锁就会启动。

批量重偏向

示例代码

创建20个锁对象,创建2个线程A、B,启动线程A,让线程B等待;在线程A中循环获取这20个锁对象;线程A执行完后,唤醒线程B,让线程B循环获取这20个已经偏向线程A的锁对象;

/*** 偏向锁-批量重偏向测试* @Author: AntonyZhang* @Date: 2021/7/6 10:25 下午**/
@Slf4j
public class BulkRebiasTest {static Thread A;static Thread B;static int loopFlag = 20;public static void main(String[] args) {try {Thread.sleep(5000);} catch (InterruptedException e) {e.printStackTrace();}final List<User> list = new ArrayList<>();A = new Thread() {@Overridepublic void run() {for (int i = 0; i < loopFlag; i++) {User a = new User();list.add(a);log.info("加锁前第 {} 次" + ClassLayout.parseInstance(a).toPrintable(), i+1);synchronized (a) {log.info("加锁中第 {} 次"+ ClassLayout.parseInstance(a).toPrintable(), i+1);}log.info("加锁结束第 {} 次"+ ClassLayout.parseInstance(a).toPrintable(), i+1);}log.info("============线程A 都是偏向锁=============");//防止竞争 执行完后唤醒线程BLockSupport.unpark(B);}};B = new Thread() {@Overridepublic void run() {//防止竞争 先睡眠线程BLockSupport.park();for (int i = 0; i < loopFlag; i++) {User a = list.get(i);//因为从list当中拿出都是偏向线程Alog.debug("加锁前第 {} 次" + ClassLayout.parseInstance(a).toPrintable(), i+1);synchronized (a) {//20次撤销偏向锁偏向线程A;然后升级轻量级锁指向线程B线程栈当中的锁记录//后面的发送批量偏向线程Blog.debug("加锁中第 {} 次" + ClassLayout.parseInstance(a).toPrintable(), i+1);}//因为前19次是轻量级锁,释放之后为无锁不可偏向//但是第20次是偏向锁 偏向线程B 释放之后依然是偏向线程Blog.debug("加锁结束第 {} 次" + ClassLayout.parseInstance(a).toPrintable(), i+1);}log.debug("新产生的对象" + ClassLayout.parseInstance(new  User()).toPrintable());}};A.start();B.start();}
}

对于线程A,可以看到,20个锁对象的对象头中markword在对象创建后三个阶段的变化情况如下:
加锁前:都是处于可偏向状态
加锁中:线程A获取该锁对象后,锁对象的对象头中的markword字段 保存了线程A的线程ID
加锁后:每个锁对象依然偏向线程A,因为此时还未唤醒线程B
线程A打印如下图所示:

对于线程B,被唤醒后,前19个锁对象在加锁前、锁定中、释放后的三个阶段,对象头中的markword字段变化情况如下(前19次打印结果都一样,取第19次结果):
加锁前:偏向线程A
锁定中:先执行撤销偏向锁流程,然后执行轻量级锁加锁流程,加锁成功后,图中锁标志位为00
释放后:执行轻量级锁解锁流程
线程B打印如下图所示:

对于线程B,第20个锁对象被获取前、锁定中、释放后的三个阶段,对象头中的markword字段变化情况如下:
加锁前:偏向线程A
锁定中:先执行撤销偏向锁流程,偏向锁撤销计数器此时已累计撤销偏向20次,达到了批量重偏向的阈值(默认为20)。因此,便会触发批量重偏向的流程,重偏向后,锁对象偏向了线程B,图中锁标志位为101
释放后:依然偏向线程B
线程B如下图所示:

关于偏向锁的偏向纪元epoch,重偏向前后的变化情况,如下图中markword从低位到高位的
第8和第9个bit位所示:
可见,重偏向后,锁对象的epoch值为10,而重偏向前,锁对象的epoch值为00

重偏向前,偏向线程A的锁对象的markword
高位                              低位
01111010 00000100 00111000 00000101重偏向后第20个锁对象的markword
11011101 00001100 10000001 00000101重偏向后,新创建的锁对象的markword
00000000 00000000 00000001 00000101

过程总结

偏向锁的批量重偏向过程:
1.匿名偏向:开启偏向锁优化,类的实例对象创建后,尚未被任何一个线程拿到该锁对象,此时锁对象处于可偏向状态。

2.偏向锁:只有一个线程A进入了拿到锁进入同步块,此时,作为锁对象的对象头中markword 的偏向锁标志为1,锁标志位为01。

3.轻量级锁:线程A执行完同步块中任务,退出同步块后(此时,该锁对象依然偏向线程A),由线程B尝试获取步骤2中创建的锁对象,因为此时线程B属于第二个线程,即使线程A已经释放了锁,锁对象还是会撤销偏向并升级为轻量级锁。
1) 撤销偏向锁:线程B尝试获取锁,此时的对象锁依然为偏向锁,所以要执行偏向锁的撤销操作:CAS将锁对象的markword修改为无锁状态,撤销偏向后,偏向锁撤销计数器加1;
2)升级轻量级锁:锁对象的markword为无锁状态,先将锁对象的markword填充到栈中Lock Record的displaced_header字段,然后CAS将栈中Lock Record的地址记录到锁对象的对象头中markword的高30位;若CAS成功,则获取轻量级锁成功,此时对象头的markword锁标志位为00。

4.批量重偏向:先将Klass的prototype_header中的epoch值加1,然后遍历每个栈帧中包含的BasicObjectLock,如果其关联的锁对象oop是该Klass,则增加该oop的对象头中的epoch值,遍历完成后将触发批量重偏向的这个锁对象oop重新偏向给当前线程。

本例中在User类的20个对象的偏向锁都撤销偏向后,偏向锁撤销计数器的值为20,达到了偏向锁批量重偏向的阈值,就会触发批量重偏向。当前线程即为线程B。

批量撤销

示例代码

创建40个锁对象,创建2个线程A、B、C,线程A先执行,让线程B等待线程A执行结束后再执行,让线程C等待线程B执行结束后再执行;

在线程A中循环获取这40个锁对象;线程A执行完后,唤醒线程B,让线程B循环获取这40个已经偏向线程A的锁对象;

线程B执行完后,唤醒线程C,让线程C循环获取这40个锁对象(根据上面

偏向锁-批量重偏向和批量撤销测试相关推荐

  1. java 偏向锁 重偏向_偏向锁的【批量重偏向与批量撤销】机制

    前言 从网上看了很多批量重偏向与批量撤销的介绍,但都一知半解,本着钻研的精神,使用数据进行分析批量重偏向与批量撤销的工作机制. 准备 首先,要先知道偏向锁的偏向锁机制,着重看下撤销机制. 然后,要知道 ...

  2. synchronized批量重偏向与批量撤销

    synchronized批量重偏向与批量撤销 批量重偏向:如果一个类的大量对象被一个线程T1执行了同步操作,也就是大量对象先偏向了T1,T1同步结束后,另一个线程也将这些对象作为锁对象进行操作,会导偏 ...

  3. 带你彻底搞懂锁膨胀,偏向锁,轻量级锁,重量级锁

    1.synchronized 我们都知道synchronized内部有四种状态,分别是:无锁.偏向锁.轻量级锁和重量级锁,所以要搞懂这几种锁之间的变化我们得对synchronized有个大致的了解. ...

  4. synchronized的偏向锁、轻量级锁和重量级锁

    文章目录 Java对象头 偏向锁 批量重偏向 批量撤销 轻量级锁 重量级锁 Monitor 原理 Java对象头 普通对象 Mark Word在64 位虚拟机中的结构为 Mark Word后三(两)位 ...

  5. 偏向锁、轻量级锁、重量级锁,Synchronized底层源码终极解析!

    synchronized是掌握Java高并发的关键知识点,底层源码更是面试重灾区.本文从源码学习synchronized的原理,讲解对象头.偏向锁.轻量级锁.重量级锁等概念. 扫码关注<Java ...

  6. 12.synchronized的锁重入、锁消除、锁升级原理?无锁、偏向锁、轻量级锁、自旋、重量级锁

    小陈:呼叫老王...... 老王:来了来了,小陈你准备好了吗?今天我们来讲synchronized的锁重入.锁优化.和锁升级的原理 小陈:早就准备好了,我现在都等不及了 老王:那就好,那我们废话不多说 ...

  7. 锁消除、锁粗化、偏向锁、自旋锁

    JVM对锁的优化:锁消除.锁粗化.偏向锁.自旋锁 synchronized底层加锁方式:偏向.自旋.轻量级锁 锁升级: 无锁 -> 偏向锁 -> 轻量级锁 -> 重量级锁 1. 锁消 ...

  8. 轻量级锁、偏向锁、重量级锁详情

    这篇文章是上篇文章是否真的理解了偏向锁.轻量级锁.重量级锁(锁膨胀).自旋锁.锁消除.锁粗化,知道重偏向吗?的补充,对于偏向锁. 这篇文章提出了对于一书"深入理解Java虚拟机"中 ...

  9. 并发系列三:证明分代年龄、无锁、偏向锁、轻量锁、重(chong)偏向、重(chong)轻量、重量锁

    前言 上篇文章咱们了解了synchronized关键字的常见用法.对象头以及证明了一个对象在无锁状态下的对象头markwork部分的前56位存储的是hashcode.接下来,咱们继续来根据对象头分别证 ...

最新文章

  1. 电脑任何字都打不出来_“有时候觉得自己一个字都写不出来了”
  2. 这一行代码,很有温度!
  3. Linked list(单链表)
  4. Leetcode 133. 克隆图 解题思路及C++实现
  5. Linux文件的权限[srwxr-xr-x]
  6. 转轮机加密详解两种解题方法 -攻防世界
  7. c++采集声卡输出_其实声卡不单单只有音效,更多功能看这篇就对了
  8. 项目范围管理——知识要点
  9. 如何删除Windows 7的保留分区
  10. 写一个http服务器
  11. c语言水仙花数(输入判断),用c语言判断一个数是否为水仙花数?
  12. 【离散数学】欧拉图与汉密尔顿图
  13. unity collider rigidbody zz
  14. Linux之ssh-agent命令
  15. [教程]智慧KTV小企鹅日志查看
  16. 【操作系统】实时调度
  17. iis6xp_xp安装iis6步骤方法
  18. java word jar包_java操作word书签生成word模板不用jar包
  19. PNP与NPN的区别与判断(一)
  20. Chrome免安装版制作

热门文章

  1. Groovy~Groovy介绍
  2. 人脑与计算机类比文献,浅析电脑与人脑的关系.docx
  3. 单集群10万节点 走进腾讯云分布式调度系统VStation
  4. 苹果手机显示已用短信服务器发送,iPhone手机一般发短信给对方,提示已送达和已读分别有什么不一样的意义。...
  5. matlab转子瞬态响应,转子热致振动现象的瞬态响应特性研究
  6. SQLServer中使用between查询日期
  7. 计算机专业的英文简历范文带翻译,计算机软件专业英文简历范文 英文简历范文带翻译...
  8. HTML中添加点击链接 进行Skype、QQ、微信对话的问题
  9. 组播地址分类 Cyrus
  10. [源码学习][知了开发]WebMagic-总体流程源码分析