在另外的两篇文章中先后介绍了轻量级同步关键字volatile和重量级锁关键字synchronized,这两个关键字是Java语言中进行线程同步的基本方式(当然还有ReentrenLock等显式锁方式)。本文将就Java虚拟机针对同步原语做的一些锁优化进行简单的介绍,同时基于JDK 1.6将这些锁优化措施设置为默认值,对锁的获取流程进行图示。

锁优化

1、自旋锁与自适应自旋

我们知道,互斥同步时候,对性能影响最大的是阻塞的实现,挂起线程和恢复线程都需要陷入内核态去完成,而频繁的用户态内核态切换势必会造成频繁的上下文切换从而影响了性能,所以自旋锁就出现了,如果共享资源的占用时间不是很长的话,想要进入临界区的线程完全可以不放弃处理器的执行时间,只要执行一个忙循环即可。但是资源的占用时间也不是我们可以凭空想象的,所以我们可以通过-XX:PreBlockSpin参数来修改自旋次数,如果忙循环的循环次数大于了设置的自旋次数,那么还是执行原来的方式挂起线程。

2、锁消除

锁消除是指虚拟机在编译器运行时,对一些代码上要求同步,但是被检测到不可能存在共享数据竞争的锁进行消除。比如在判断一段代码中,堆上的数据都不会逃逸出去从而被其他的线程访问到,那么可以认为他们是线程私有的,同步加锁自然无须进行。

3、所粗化

我们熟悉的是减小锁的粒度如锁分解和锁分段等优化措施,但有的时候,如果一系列的连续操作对都对一个对象反复加锁和减锁,甚至枷锁操作是出现在循环体中的,那么即使没有线程竞争,频繁的进行互斥同步操作也会导致不必要的性能损耗。(注意:这项优化措施似乎在有了偏向锁和轻量级锁后就不太需要了—个人感觉)

4、轻量级锁

轻量级锁匙JDK 1.6之后新加入的新型锁机制,它的实现和后面的偏向锁的实现都需要Java对象头MarkWord信息的帮助

 

对象头中的MarkWord信息是可以复用的,复用的具体情况如上图所示。在代码进入同步块时,如果此对象没有被锁定(锁标志位“01”状态),虚拟机将在当前线程的栈帧中建立一个名为锁记录(Lock Record)的空间,用于存储锁对象目前的MarkWord拷贝(官方把这份拷贝加了一个Displaced前缀,即Displaced Mark Word)

 

然后,虚拟机将使用CAS操作尝试将对象的Mark Word更新为指向Lock Record的指针,如果这个更新成功了,那么这个线程就拥有了该对象的锁,并且对象Mark Word的锁标志位(Mark Word的最后2bit)将转变为“00”,即表示此对象处于轻量级锁定状态

 

如果更新失败了,虚拟机首先会检查对象的Mark Word是否指向当前线程的栈帧,如果只说明了当前线程已经拥有了这个对象的锁,那就可以直接进入同步块执行,否则则说明这个锁对象已经被其他线程抢占了。如果有两个不同的线程抢占同一个锁,那么轻量级锁就会膨胀为重量级锁(互斥同步)

5、偏向锁

偏向锁的目的是消除数据在无竞争情况下的同步原语,进一步提高程序的运行性能。轻量级锁匙在无竞争的情况下使用CAS操作去消除同步使用的互斥量,那偏向锁就是在无竞争的情况下把整个同步都消除掉,连CAS也不要了。它的实现原理和轻量级锁一样,也是和对象头的Mark Word相关。

结合所有的锁优化步骤,那么代码执行锁获取操作的流程如下图所示

 

参考资料:《深入理解Java虚拟机》—周志明

版权声明:本文为lybnumber6原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://www.cnblogs.com/lybnumber6/p/12177697.html