我们在开发多线程时,为了解决线程安全问题,我们通常采用的方法就是用加锁的方式来解决。但我们知道虽然加锁的方式的确能够解决线程安全问题,但程序在运行时多多少少会有些性能的损耗,因为程序在运行时每次都要频繁的执行获取锁和释放锁的操作。在虚拟机中为了解决这方面的性能损耗,通常情况下会对我们加的锁进行优化,目的就是提高程序的运行效率。锁优化主要分为5种,它们分别是:自旋锁、锁消除、锁粗化、轻量级锁、偏向锁等。在这一篇中我们主要介绍第一种锁优化也就是自旋锁。
- 自旋锁
我们知道线程同步是用线程阻塞的方式来实现的。也就是说如果多个线程使用的是同一个锁,那么在当前时刻,只允许执行一个线程,而其它的线程会频繁的执行暂停和恢复操作。但在实际的程序运行时,共享数据的锁定是很短暂的,如果为了这短暂的时间,要执行线程的暂停和恢复操作,这显然是很不值得的,因为频繁的执行线程的暂停和恢复操作会影响程序的运行效率。为了解决上述的问题,虚拟机于是采用了自旋锁的方式来解决上述问题。自旋锁优化的方式是如果某一个线程在等待其它线程释放锁,那么虚拟机执行时并不会将当线程暂停,也不会让当前线程释放CPU的执行时间,而是让当前线程执行一个循环,通过这个循环的执行时间来等待其它线程释放锁。这种锁的优化方式就是自旋锁。
自旋锁并不能代替线程的阻塞,它的目的是为了解决线程频繁的执行暂停和恢复也就是线程切换而存在的。如果其它线程占用锁的时间较短,那么自旋锁的优化方式效果就会非常明显。反之,如果其它线程占用锁的时间很长,那么自旋锁的优化方式就显示不合适了,因为它的这种优化方式只会白白消耗处理器的资源,而不会做任何有用的工作,因为自旋只是执行了一个空循环而已,不但不会达到优化的效果,反而会带来性能上的浪费。所以为了解决上述问题,自旋锁一定有某种条件的限制,而不能让自旋锁一直等待下去。所以在虚拟机中有规定,自旋锁循环的次数默认是10次。也就是说,如果当前线程在执行自旋循环时,如果超过了10次,那么当前线程将不在执行下去了,而是采用传统的方式,也就是暂停当前线程的执行。当然用户也可以自己配置自旋锁循环的次数。使用参数-XX:PreBlockSpin
修改。
自旋锁本质上只有一种,但虚拟机为了更好的优化锁于是在JDK 1.6中引入了自适应的自旋锁。自适应自旋锁的意思就是循环的次数不是上述所说的默认10次了。而是根据上一个线程获取到锁时它的自旋时间来决定的。虚拟机在执行时,如果发现自旋完成后成功获得到了锁,那么虚拟机就会认为这次自旋优化很成功,并且很有可能再一次成功。从而虚拟机会将自旋等待的时间在延长一些,也就是增加循环的次数,从而保证其它线程在自旋后也能成功获取到锁。除此之外自适应自旋锁还会检测,如果发现对于某一个锁,自旋完成后很少成功的获得锁,那么在以后要获取这个锁时将尽可能的省略掉自旋的过程,以避免浪费处理器的资源。