自旋锁与适应性自旋锁
概念引入
在介绍自旋锁之前,我们需要介绍一些前提知识来帮助大家理解自旋锁的概念。
阻塞或唤醒一个Java进程,需要操作系统切换CPU状态来完成,这种状态转换需要耗费处理器时间。如果同步代码块中的内容过于简单,状态转换花费的时间有可能比用户代码执行的时间还长。
在许多场景中,同步资源的锁定时间很短,为了这一小段时间去切换线程,线程挂起和恢复现场的时间花费可能会让系统得不偿失。如果物理机器有多个处理器,可以让两个或以上的线程并行执行,我们就可以让后面那个请求锁的线程不放弃CPU的执行时间,看看前面那个得到锁的线程是否会很快释放锁。
而为了让当前线程“稍等一下”,我们就需要让当前线程进行自旋,如果自旋完成后,持有锁的线程已经释放了锁,当前线程就可以不进入阻塞状态而是直接获得同步资源,避免的线程切换的开销,这就是自旋锁。
自旋锁与非自旋锁流程图
自旋锁的缺陷
自旋等待虽然避免了线程切换的开销,但它要占用处理器时间。如果锁被占用的时间很短,自旋的效果就很好。反之,如果所被占用的时间很长,自旋就是在白白浪费处理器时间。所以,自旋等待的时间必须要有限度,默认情况下是10次,也可以通过
-Xx:PreBloackSpin来更改。如果在自旋10次都没有获得锁,就应该挂起线程。
自旋锁的实现原理
自旋锁的实现原理同样也是CAS,AtomicInteger中调用unsafe进行自增操作源码中的do…while循环就是一个自旋操作,如果修改数值失败则通过循环来执行自旋,直到成功。
自适应自旋锁
自旋锁在Java1.6中改为默认开启,并引入了自适应的自旋锁。
自适应意味着自旋的次数不在固定,而是由前一次在同一个锁上的自旋时间和锁的拥有者的状态共同决定。
如果在同一个锁对象上,自旋等待刚刚成功获得过锁,并且持有锁的线程正在运行中,那么虚拟机就会认为这次自旋也是很可能再次成功的,进而它将会允许线程自旋相对更长的时间。
如果对于某个锁,线程很少成功获得过,则会相应减少自旋的时间甚至直接进入阻塞的状态,避免浪费处理器资源。