自旋锁CAS:
CAS:Compare and Swap, 即比较交换。
在轻量级锁升级为重量级锁时就用到了自旋锁CAS,同时CAS也可以看作是一种比较交换的思想,在数据库里面的乐观锁也有CAS的思想。通过无锁的操作实现对数据的更新(单个变量),某种程度上保证了数据的一致性和线程的安全性。
CAS算法在Unsafe.class里面的native方法:
public final native boolean compareAndSwapObject(Object var1, long var2, Object var4, Object var5);
CAS算法:CAS是一种无锁算法,CAS有3个参数,内存值V,预期值E,要修改的值N。如果内存中要检测的值V和预期值E相同,则把内存值V修改成目标N,否则则什么都不做。
CAS是通过无限循环来获取数据的,若果在第一轮循环中,a线程获取地址里面的值被b线程修改了,那么a线程需要自旋,到下次循环才有可能机会执行。
CAS存在的三大问题:
1.经典ABA问题
CAS是在需要操作值的时候,检查值有没有变化,如果没有发生变化则更新,但是如果一个值原来是A,变成了B,又变成了A,那么CAS进行检查的时候发现它的值没有发生变化,但是实际上却变化了。ABA问题的解决思路是,每次变量更新的时候把变量的版本号加1,那么A-B-A就会变成A1-B2-A3,只要变量被某一线程修改过,改变量对应的版本号就会发生递增变化,从而解决了ABA问题。从Java 1.5开始,JDK的Atomic包里提供了一个类AtomicStampedReference来解决ABA问题。这个类的compareAndSet方法的作用是首先检查当前引用是否等于预期引用,并且检查当前的标志是否等于预期标志,如果全部相等,则以原子方式将该应用和该标志的值设置为给定的更新值,这样CAS操作中的比较就不依赖于变量值了。
2.自旋循环时间消耗
自旋CAS如果长时间不成功,会给CPU带来非常大的执行开销。解决办法:1.代码层面,破坏掉for死循环,当超过一定时间或者一定次数时,return退出。2. 使用类似ConcurrentHashMap的方法。当多个线程竞争时,将粒度变小,将一个变量拆分为多个变量,达到多个线程访问多个资源的效果,最后再调用sum把它合起来,能降低CPU消耗,但是治标不治本。3.使用JVM能支持处理器提供的pause指令来提升效率。pause指令有两个作用:第一,它可以延迟流水线执行指令(de-pipeline),使CPU不会消耗过多的执行资源,延迟的时间取决于具体实现的版本,在一些处理器上延迟时间是零;第二,它可以避免在循环的时候因内存顺序冲突(Memory Order Violation)而引起CPU流水线被清空,从而提高CPU的实行效率。
3.CAS只能单变量
当对一个共享变量执行操作时,我们可以使用循环CAS的方式来保证原子操作,但是对多个共享变量操作时,循环CAS就无法保证操作的原子性,这个时候可以用锁。还有一个取巧的办法,就是把多个共享变量合并成一个共享变量来操作。比如,有两个共享变量i=2,j=a,合并一下ji=2a,然后用CAS来操作ij。从Java 1.5开始,JDK提供了AtomicReference类来保证引用对象之前的原子性,就可以把多个变量放在一个对象里来进行CAS操作。