轻量级锁在没有竞争时(就自己这个线程),每次重入仍然需要执行CAS操作。
Java6 中引入了偏向锁来做进一步优化:只有第一次使用CAS将线程ID设置到对象的Mark Word头,之后发现这个线程ID是自己的就表示没有竞争,不用重新CAS,以后只要不发生竞争,这个对象就归该线程所有。
我们看上述代码
每次进行CAS,且需要创建锁记录,均会影响CPU性能
对象头格式
一个对象创建时:
- 如果开启了偏向锁(默认开启),那么对象创建后,Mark Word 的后三位为101,这时他的thread,epoch,age都为0
- 偏向锁时默认延迟的,不会在程序启动时立即生效,如果想避免延迟,可以加VM参数 - XX : BiasedLockingStartupDelay来禁用延迟
- 如果没有开启偏向锁,那么对象创建后,markword值为0x01
程序刚刚启动就立即创建对象,发现后三位是001,即无锁状态。(JOL工具查看对象的对象头)
对程序改写,加一个sleep
可以发现几秒后,创建的对象,就会变成有锁的了,加锁就仅仅将线程id存入对象头中
处于偏向锁的对象解锁后,线程id仍存储于对象头中。
调用一个对象的hashCode()方法,会禁用这个对象的偏向锁
为什么调用对象的哈希码就会禁用对象的偏向锁呢?
当对象处于偏向锁的状态的时候,存完线程id等之后,就没地方存储hashcode了
为什么轻量级锁和重量级锁和重量级锁就不需要在对象头中存储hashcode呢?
因为轻量级锁的hashcode存储在锁对象中,而重量级锁的hashcode存储在Monitor对象中
输出的结果如下,首先是未加锁状态,加锁之后,将t1线程的线程id放入对象头中,
解锁后,线程id依然会在对象头中,即第三行的输出和第二行的输出一致。
进入t2线程,打印d对象的对象头
然后t2线程尝试加锁,再次打印对象头,加的是状态为00的轻量级锁
最后t2线程释放锁,最终状态为001,不可偏向状态。
调用 wait/notify 也会撤销偏向锁状态,升级为重量级锁。
锁消除
Score是对两个代码的执行时间的评分,我们发现,评分基本相同,为什么第二段代码加了锁,执行所消耗的时间却和第一段代码基本一致呢
Java运行时有一个JIT即时编译器,会对Java代码的热点代码,进行优化,其中一个手段锁消除,检测到上面代码的Object锁对象,不会从这个方法中逃逸,即不会被共享,那么加锁就毫无意义了,synchronized的就直接被优化掉了,即b()方法根本就没有被加锁。
当关闭锁消除的时候,发现时间明显上升了