synchronized在JDK1.5中性能比较低。不过,经过后续版本的各种优化迭代,其性能也得到了前所未有的提升。在上一篇文章中,我们谈到了通过锁扩展来提升synchronized的性能,但它只是“众多”synchronized性能优化方案中的一种,因此本文将对synchronized的核心优化方案进行盘点。synchronized核心优化方案主要包括以下四种:锁扩展锁消除锁粗化自适应自旋锁1.锁扩展我们先回顾一下锁扩展对synchronized性能的影响。所谓锁扩容,是指从无锁升级到同步锁,到偏向锁,再到轻量级锁,最后到重量级锁的过程称为锁扩容或锁升级。在JDK1.6之前,synchronized是一个重量级的锁,也就是说synchronized在释放和获取锁的时候会从用户态转换到内核态,但是转换效率比较低。但是有了锁扩展机制,synchronized状态多了无锁、偏向锁、轻量级锁。此时,在进行并发操作时,大部分场景都不需要从用户态到内核态的转换。这大大提高了同步的性能。PS:至于为什么不需要从用户态到内核态的转换呢?关于锁扩展的文章请移步:《synchronized 优化手段之锁膨胀机制》。2.锁淘汰很多人都知道synchronized中锁扩展的机制,但是对接下来的三个优化不是很了解,这样就会错过面试的好机会,所以本文就分别来说说这三个优化。.锁消除是指在某些情况下,如果JVM虚拟机检测不到某段代码被共享竞争的可能性,就会消除这段代码所属的同步锁,从而提高程序的性能。消除锁的基础是逃逸分析的数据支持,比如StringBuffer的append()方法,或者Vector的add()方法,很多时候是可以进行消除锁的,比如下面的代码:publicStringmethod(){StringBuffersb=newStringBuffer();对于(inti=0;i<10;i++){sb.append("i:"+i);}returnsb.toString();}上面的代码经过编译部分代码如下:从上面的结果可以看出,我们之前写的线程安全的带锁StringBuffer对象在之后被替换成了一个解锁的不安全的StringBuilder对象生成了字节码,因为StringBuffer变量属于局部变量,不会从这个方法中逃逸,所以这时候我们可以使用锁消除(nolock)来加速程序的运行。3.锁粗化锁粗化是指将多个连续的加锁和解锁操作连接在一起,扩展成一个范围更大的锁。只听说锁“细化”可以提高程序的执行效率,也就是尽可能缩小锁的范围,这样在锁竞争的时候,等待获取锁的线程可以获取到更早加锁,从而提高程序的运行效率,但是锁粗化是如何提高性能的呢?没错,锁细化的观点在大多数情况下是成立的,但是一系列连续的加锁和解锁操作也会造成不必要的性能开销,从而影响程序的执行效率,比如这段代码:publicStringmethod(){StringBuildersb=newStringBuilder();for(inti=0;i<10;i++){//伪代码:锁操作sb.append("i:"+i);//伪代码:解锁操作}returnsb.toString();}这里不考虑编译器的优化。如果在for循环中定义锁,锁的作用范围很小,但是每次for循环都需要加锁和释放锁,性能很低;但是如果我们直接在for循环的外层加一个锁,那么这段代码对于同一个对象操作的性能就会有很大的提升,如下伪代码所示:publicStringmethod(){StringBuildersb=new字符串生成器();//伪代码:锁操作for(inti=0;i<10;i++){sb.append("i:"+i);}//伪代码:解锁操作returnsb.toString();}锁粗化的作用:如果检测到同一个对象连续进行了加锁和解锁操作,则将这一系列操作合并成一个更大的锁,从而提高程序的执行效率。4.自适应自旋锁自旋锁是指一种通过自身循环尝试获取锁的方式。伪代码实现如下://Trytoacquirealockwhile(!isLock()){}自旋锁的好处是避免了某些线程的suspend和resume操作,因为suspend线程和resume线程需要从用户态转移到内核态,这个过程比较慢,所以通过自旋可以在一定程度上避免线程suspend和resume操作。性能开销。但是如果自旋时间长了还不能获取到锁,也会造成一定的资源浪费,所以我们通常给自旋设置一个固定的值,避免一直自旋带来的性能开销。不过,对于synchronized关键字来说,它的自旋锁就更“聪明”了。synchronized中的自旋锁是自适应自旋锁,就像之前开的手动挡三轮车,但是经过JDK1.6优化后,我们的“车”一下子变成了自动挡的兰博基尼。自适应自旋锁是指线程自旋的次数不再是一个固定值,而是一个动态变化的值。这个值会根据上一次自旋获取锁的状态来决定本次的自旋次数。比如上次通过自旋成功获取到锁,那么这次也有可能通过自旋获取到锁,所以这次自旋的次数会增加,如果上次没有通过自旋成功获取到锁,那么本次自旋可能无法获取到锁,所以为了避免资源浪费,会少一些或者不用循环来提高程序的执行效率。简单来说,如果线程自旋成功,则下一次自旋的次数会增加,如果失败,则下一次自旋的次数会减少。总结在本文中,我们介绍了synchronized的四种优化方案,其中锁扩展和自适应自旋锁是synchronized关键字本身的优化实现,而锁消除和锁粗化则是JVM虚拟机为synchronized提供的优化方案。这些优化方案最终大大提升了synchronized的性能,也让它在并发编程中占有一席之地。Reference&Acknowledgmentswww.cnblogs.com/aspirant/p/11470858.htmlzhuanlan.zhihu.com/p/29866981tech.meituan.com/2018/11/15/java-lock.html本系列推荐文章同时发布第一课:Thread详细解释Java中用户线程和守护线程的区别?深入理解ThreadPool线程池的7种创建方式,强烈推荐大家使用……池化技术到底有多牛?看到线程和线程池的对比,惊呆了!并发中的线程同步和locksynchronized锁this和class的区别!volatile和synchronized的区别轻量级锁就一定比重量级锁快吗?这样终止线程会导致服务宕机?SimpleDateFormat线程不安全的5个解决方案!ThreadLocal不好用?这就是你没用的原因!ThreadLocal内存溢出代码演示及原因分析!信号量告白:限流器我用对了!CountDownLatch:别招手了,等大家再次加入我们!CyclicBarrier:当所有人都准备好后,司机就可以发动汽车了!synchronized优化方法的锁扩展机制!关注公众号“Java中文社区”,查看更多有趣的Java并发知识文章。
