首先把pom引入。为了打印对象头
直接开搞,新建一个SyncDemo类,建立两个变量,int a 与 float b
新建一个SyncTest类,整个main方法,里面打印一下SyncDemo对象头,记得pom引入jol,不然ClassLayout方法没有。
注意:偏向锁会延迟开启,也就是说main线程启动之后要过一段时间偏向锁才会开启。
我们可以通过sleep让线程睡眠一下,或者设置关闭延迟(-XX:BiasedLockingStartupDelay=0)
睡眠一下,让偏向锁启动。这时候看到状态为101。
下面中途插播一个广告,从别处而来,正规途径:
这个图可能大家都眼熟,来自jdk,上面说明了对象头存储了些啥
看图上文字。其中可以看到int 和float我们没有给他们赋予值,但是他们的值为0,因为会给他们赋予初始值。
对象头:mark word 8字节,class point 4字节
圈起来的就属于对象头中的Class内存地址(指针)
001表示无锁状态,计算了HashCode之后就无法使用偏向锁,因为头部存储了HashCode,无法存储线程ID。
十六进制和存储的位置是不是很像?
十三:哪里像了?
稍等,姿势不对,十六进制调转一下,7d f2 16 8,这不是很像,是一模一样
十三:为啥会导致这个玩意呢。
因为是高字节存在高地址中(小端存储),所以我们看的时候要倒过来看,就如上图前8个字节要倒过来看,01 7d f2 16 08 00 00 00-->00 00 00 08 16 f2 7d 01,现在你在来看,就等于我们打印的十六进制。【数据在内存中存储分为大小端模式,小端:高字节保存在内存的高地址中. 大端:高字节保存在内存的地地址中,具体可以百度一波:大小端模式】
广告插播结束,回归到代码。
我们给他上锁,看下对象头中有什么变化。
101为偏向锁,SyncDemo的对象头存储了偏向的线程ID。
这个时候疑问就来了,为什么上面同样是101,但是头部对象的值不一样?不是说对象头都会存储线程id的吗?为什么上面那一幅图没有呢。
注意:到底是不是偏向锁,不仅要看101,还要看对象头中有没有存储偏向线程ID,
存储了线程ID则为偏向锁(已经偏向),没有存储线程ID,则为无锁。
调用SyncDemo.hashCode之后,因为对象头存储了hashCode导致状态变成了00,00是轻量锁
我们再搞一个线程,进行加锁。
可以看到第一个打印的是偏向锁,第二个就是变成轻量锁了00。
继续搞一个线程,现在是两个线程了。
这个时候状态就变成了10了,这是一把重量锁,重量锁是非常耗性能的。
我们来看一波class里面有啥,javap -v SyncTest.class命令,可以查看到
可以看到synchronized加锁和解锁都是自己进行处理的,贼简单只要一个synchronized,其它都不用做了,所以导致无脑加synchronized,这样或许会导致性能问题(重量锁),要根据问题对症下药。
总结:
锁的类型:
1、偏向锁
对象刚开始是无锁,经过synchronized之后是偏向锁,头部存储着线程ID。
2、轻量锁
synchronized方法之后,其它线程也调用synchronized,此时锁变成了轻量锁。这个时候头部存储的值是栈中锁记录的指针。
3、重量锁
调用synchronized方法之后,此时是偏向锁,这个时候有线程并发执行synchronized,竞争锁资源,这个时候会升级成重量锁。这个时候就很耗性能了,因为要调用系统内核进行线程互斥。