CAS(Compare-and-Swap),即比较并替换,java并发包中许多Atomic的类的底层原理都是CAS。java
它的功能是判断内存中某个地址的值是否为预期值,若是是就改变成新值,整个过程具备原子性。多线程
具体体现于sun.misc.Unsafe类中的native方法,调用这些native方法,JVM会帮咱们实现汇编指令,这些指令是CPU的原子指令,所以具备原子性。并发
1 public class CASDemo { 2 3 public static void main(String[] args) { 4 5 //初始值5 6 AtomicInteger atomicInteger = new AtomicInteger(5); 7 8 //和5比较,设置为10 9 System.out.println("预期值:5,当前值:"+atomicInteger); 10 System.out.println("是否设置成功:"+atomicInteger.compareAndSet(5, 10)); 11 //和5比较,设置为15 12 System.out.println("预期值:5,当前值:"+atomicInteger); 13 System.out.println("是否设置成功:"+atomicInteger.compareAndSet(5, 15)); 14 15 System.out.println("当前值:"+atomicInteger); 16 } 17 }
输出为:this
预期值:5,当前值:5 是否设置成功:true 预期值:5,当前值:10 是否设置成功:false 当前值:10
下面看一下getAndAddInt在底层Unsafe类中的代码(自旋锁),运用到了CASatom
//va1为对象,var2为地址值,var4是要增长的值,var5为当前地址中最新的值
public final int getAndAddInt(Object var1, long var2, int var4) { int var5; do { var5 = this.getIntVolatile(var1, var2); } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4)); return var5; }
首先经过volatile的可见性,取出当前地址中的值,做为指望值。若是指望值与实际值不符,就一直循环获取指望值,直到set成功。spa
适用场景:线程
1. CAS 适合简单对象的操做,好比布尔值、整型值等;code
2. CAS 适合冲突较少的状况,若是太多线程在同时自旋,那么长时间循环会致使 CPU 开销很大;对象
CAS的缺点:blog
1. CPU开销过大 : 在并发量比较高的状况下,若是许多线程反复尝试更新某一个变量,却又一直更新不成功,循环往复,会给CPU带来很到的压力。
2. 不能保证代码块的原子性:CAS机制所保证的知识一个变量的原子性操做,而不能保证整个代码块的原子性。好比须要保证3个变量共同进行原子性的更新,就不得不使用synchronized了。
3. ABA问题:若是内存地址V初次读取的值是A,在CAS等待期间它的值曾经被改为了B,后来又被改回为A,那CAS操做就会误认为它历来没有被改变过。
ABA问题以及解决:使用带版本号的原子引用AtomicStampedRefence<V>,或者叫时间戳的原子引用,相似于乐观锁。
0 // ABA问题及解决方式
1 public class ABADemo { 2 3 private static AtomicReference<String> atomicReference = new AtomicReference<>("A"); 4 private static AtomicStampedReference<String> stampReference = new AtomicStampedReference<>("A",1); 5 6 public static void main(String[] args){ 7 new Thread(()->{ 8 //获取到版本号 9 int stamp = stampReference.getStamp(); 10 System.out.println("t1获取到的版本号:"+stamp); 11 try { 12 //暂停1秒,确保t1,t2版本号相同 13 TimeUnit.SECONDS.sleep(1); 14 } catch (InterruptedException e) { 15 e.printStackTrace(); 16 } 17 atomicReference.compareAndSet("A","B"); 18 atomicReference.compareAndSet("B","A"); 19 20 stampReference.compareAndSet("A","B",stamp,stamp+1); 21 stampReference.compareAndSet("B","A",stamp+1,stamp+2); 22 System.out.println("t1线程ABA以后的版本号:"+stampReference.getStamp()); 23 24 },"t1").start(); 25 26 new Thread(()->{ 27 //获取到版本号 28 int stamp = stampReference.getStamp(); 29 System.out.println("t2获取到的版本号:"+stamp); 30 try { 31 //暂停2秒,等待t1执行完成ABA 32 TimeUnit.SECONDS.sleep(2); 33 } catch (InterruptedException e) { 34 e.printStackTrace(); 35 } 36 System.out.print("普通原子类没法解决ABA问题: "); 37 System.out.println(atomicReference.compareAndSet("A","C")+"\t"+atomicReference.get()); 38 System.out.print("版本号的原子类解决ABA问题: "); 39 System.out.println(stampReference.compareAndSet("A","C",stamp,stamp+1)+"\t"+stampReference.getReference()); 40 41 },"t2").start(); 42 } 43 }
输出结果:普通原子引用类在另外一个线程完成ABA以后继续修改(把A改为了C),带版本号原子引用有效的解决了这个问题。
t1获取到的版本号:1 t2获取到的版本号:1 t1线程ABA以后的版本号:3 普通原子类没法解决ABA问题: true C 版本号的原子类解决ABA问题: false A