我记得我刚毕业那年(说的很老,其实就是17年 !),一听到分布式,高并发这些相关的词,就不明觉厉,不时的发出几声 oh my gad,等到我开始接触高并发业务的时候,就变成了天天mmp。。。。。这是啥。。。我在哪。。。我是谁。。。
还记得那是17年的夏天,我找到了我的第一份工作,是一家上万人规模的互金公司(这里我就不打广告了 ^ _ ^),当时是来公司的第二周,领导突然给了一个任务。
领导:你去听一下这个需求,记得有什么不懂的就问,排期的话可以考虑考虑晚点给。
我:好的,领导放心。(此时的内心:你咋不跟我一起去,可怕。)
不知不觉我来到了风水还不错,就是偶尔掺杂了一点汗水和脚上不明味道的会议室。
产品:巴拉巴拉,巴巴拉拉。
我:emmmmmmmmm
一个小时后。。。。。我默默的走出了会议室
之后我和另外一个同学(一个魁梧大汉)的讨论下,我大致明白了应该如何实现,我们本次要做的是在公司的TO B业务系统APP端(一个金融业务报单系统,相当于别的公司或商户向我们公司借钱时提交的一些资料和金额)提报时增加一个总额度的限制(原先的额度限制都是在提交上来之后,走智能风控时才做校验,然后校验失败才退回),现在要做到,别的公司提交时就马上就判断出来他的额度还够不够。
因为刚刚双非大学本科毕业,我并不是很了解锁这个东西,只是有大概的了解,但是我还是知道这种业务肯定是要用到锁的,不用肯定是要出大问题的,所以我很快就去向我们组的老油条们请教了一下锁在用的时候有哪些要注意的地方,,,然后就开始了一下午的巴拉巴拉。
在第二天一天知识和漫天口水的熏陶下,我大致了解了几种锁的注意事项,有synchronized JUC Lock Reids分布式锁 zk分布式锁 等等.
接下来我们就一起聊一聊synchronized
- 俗话说,JAVA中一切都是对象,我们一起先了解一下JAVA对象在内存中的结构,此处只做简单了解,后面我会根据情况写一些深入了解JAVA对象的文章(包括registerNatives(),对象的HashCode等)废话不多,上图。
- 图1中 Soga是我自定义的一个类,它实例化后,我们调用这个实例化后的类,发现除了我们自定义的一个类中方法(solega), 还有额外的很多方法,由图2可知,这些方法来自于我们的Object。
- 综上我们也可以粗略的认为 类是方法的载体,它继承于对象(详细的我会再单独出一章博客)
- JAVA中的对象分为:对象头(对象头又分为markword和klassword),实例数据,内存填充部分。
- 如上图3,Soga是一个new出来的对象,它有两个属性,age和name。我们看控制台输出(调用JOL的方法,JOL可以输出对象的一些内部外部信息,如果对JOL有兴趣的,可以自行google)。
- 它的前3行代表对象头信息,4、5行是实例数据,也就是我们的age和name的值,6行是内存填充部分
- 对象头信息中,前两行是markword,用于标记锁、GC数据等,我们在GC中总说的对象年龄就存在这里。
- 第三行Klass Pointer类指针,用于标记这个对象是哪个类的
- 4、5行是对象的实例数据
- 6行是内存填充,因为JVM规定,一个对象占用的字节必须是8的倍数。
我们本章所要研究的锁数据和对象头有着千丝万缕的关系。引用周志明大神的《深入理解JVM虚拟机》中的一幅图。下图中我们暂时只关注偏向模式和标志位两部分其中标志位,01标识无锁或偏向锁,偏向模式为0时为无锁,为1时是偏向锁。标志位00为轻量级锁,10位重量级锁
无锁状态状态中 对象头的markword的部分有对象的hash码,也叫一致性哈西码。转化为偏向锁时,存的是线程ID epoch 年龄等数据。
1. 偏向锁
偏向锁简介:偏向锁是当一个线程获取到当前对象的锁之后,就一直偏向于当前线程,如果在接下来的执行过程中,该锁一直没有被其他的线程获取,则持有偏向锁的线程将永远不需要再进行同步,(启用参数-XX:+UseBiased Locking,这是自JDK 6起HotSpot虚拟机的默认值)
- 此时soga对象是无锁的,当前框内的 标志位为01,然后我们开始给soga对象加偏向锁。
- 上图中,我们把soga这个类加锁后,我们的对象头中的markword出现了变化,但是正常来说,我们当前只加了一个锁,标志位应该是01才对,现在怎么是00.。。。卧槽。
经过作者经过了一下午,ca。翻阅hotspot源码后发现
源码路径: src/share/vm/runtime/globals.hpp
product(intx, BiasedLockingStartupDelay, 4000,
"Number of milliseconds to wait before enabling biased locking")
range(0, (intx)(max_jint-(max_jint%PeriodicTask::interval_gran)))
constraint(BiasedLockingStartupDelayFunc,AfterErgo)
//在JVM启动时,有个偏向锁4000毫秒的延迟加载
BiasedLockingStartupDelay, 4000
- 然后作者将源码延迟4500ms后再执行
这回就没毛病了 - 经过作者查阅资料和源码发现。当jvm启动时,初始化的对象加偏向锁会耗费大量资源,这样可以减少偏向锁撤销的成本(jvm的偏向锁的优化) 注意校验除了轻量锁之外的其他锁时,要将主线程4500毫秒休眠去掉
2.轻量级锁
轻量级锁简介:轻量级锁是JDK 6时加入的新型锁机制,当对象锁交替竞争时,锁升级为轻量级锁。在代码即将进入同步块的时候,如果此同步对象没有被锁定(锁标志位为“01”状态),虚拟机首先将在当前线程的栈帧中建立一个名为锁记录(Lock Record)的空间,用于存储锁对象目前的Mark Word的拷贝(官方为这份拷贝加了一个Displaced前缀,即Displaced MarkWord)然后,虚拟机将使用CAS操作尝试把对象的Mark Word更新为指向LockRecord的指针。如果这个更新动作成功了,即代表该线程拥有了这个对象的锁,并且对象Mark Word的锁标志位(Mark Word的最后两个比特)将转变为“00”,表示此对象处于轻量级锁定状态。
- 上图展示了偏向锁升级为轻量锁标志位升级为00
3.重量级锁
重量级锁简介 :当两个线程出现同时竞争一个锁时,锁升级为重量级锁。
- 上图展示升级为重量锁,标志位升级为10
重量级锁的释放会有延迟
4.总结
- 对象分为-- 对象头、对象实例数据、内存补充位
- 对象头中包含 markword(其中有对象年龄,锁等信息,如果是数据还包含数据长度),klassword(其中有类的引用,标识这个对象属于哪个类)
- 锁分为-- 偏向锁、轻量级锁、重量级锁。
- 偏向锁的标志位为01,偏向锁标识为1.
- 轻量级锁标志位为00
- 重量级锁标志位为10
- 在32位的HotSpot虚拟机中,对象未被锁定的状态下,Mark Word的32个比特空间里的25个比特将用于存储对象哈希码,4个比特用于存储对象分代年龄,2个比特用于存储锁标志位,还有1个比特固定为0(这表示未进入偏向模式)
- 偏向锁在升级为轻量级锁时。(引用周志明《深入理解JAVA虚拟机》)
1.虚拟机首先将在当前线程的栈帧中建立一个名为锁记录(Lock Record)的空间,用于存储锁对象目前的Mark Word的拷贝.
2.将锁对象的对象头中的MarkWord复制到线程的刚刚创建的锁记录中。
3.将锁记录中的Owner指针指向锁对象。
4.将锁对象的对象头的MarkWord替换为指向锁记录的指针
如果哪里写的有问题,还辛苦大家赐教。