CAS(无锁优化 自旋)

480 阅读2分钟

定义

  • CAS(CompareAndSwap,CompareAndSet)比较并替换,核心逻辑是 先获取当前的值,传入期望值和要修改成的值,如果期望值和现有的值匹配,就将值改成传入的要修改成的值。
  • CAS是CPU的一个指令(需要JNI调用Native方法,才能调用CPU的指令),因此保证了原子性操作,不会在比较并替换的时候被其他线程修改
  • CAS是非阻塞的、轻量级的乐观锁

作用

  • 因为是无锁,非阻塞、轻量级的乐观锁,许多底层使用的都是CAS,例如:java.util.concurrent.atomic的原子类,Unsafe,自旋锁等等...

原理

  • 可以通过分析原子类来了解CAS的实现原理。 1.AtomicInteger类
 AtomicInteger count = new AtomicInteger(0); 
 count.incrementAndGet();//count++
 
 //自增本来在多线程中是要加同步的,这里使用的是unsafe类中的方法
  private static final Unsafe unsafe = Unsafe.getUnsafe();
  
  public final int incrementAndGet() {
        return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
    }

2.Unsafe类

jdk1.8使用反射获取实例,jdk1.9版本提供一个获取实例的方法,jdk11以后就禁止了
  @CallerSensitive //只能由开发者调用,其他无权限
    public static Unsafe getUnsafe() {
        Class var0 = Reflection.getCallerClass();
        if (!VM.isSystemDomainLoader(var0.getClassLoader())) {
            throw new SecurityException("Unsafe");
        } else {
            return theUnsafe;
        }
    }
    
   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;
    }  
    /**
  *  CAS
  * @param o         包含要修改field的对象
  * @param offset    对象中某field的偏移量
  * @param expected  期望值
  * @param update    更新值
  * @return          true | false
  *通过对象和偏移量获取当前的值,再和期望值对比,如果相同就替换为更新值,返回结果
  */
    public final native boolean compareAndSwapInt(Object o, long offset, int expected, int update);

ABA问题

  • 问题:CAS的核心是比较并替换,在比较的时候只是比对值是否相同,如何有这么一种情况:一个线程先将值设置为A,另一个线程获取到当前值为A并作为期望值,于此同时有一个线程将值从A改为B又改为A,这时候将A作为期望值的线程进行比较时发现是相同的从而进行替换,但是并不知道其实这已经不是从前的那个A了。 在基本数据类型没什么问题,如果是引用数据类型就有可能导致异常。

  • 解决方式 加标记,使用 AtomicStampedReference

//创建对象的时候设置值和版本号
private final static AtomicStampedReference<String> ar = new AtomicStampedReference<>("A", 1);
//调研CAS时会比对期望的值和版本号,
boolean isCas = ar.compareAndSet(A, B, 1,2)
/**
      * @param expectedReference 引用的期望值
      * @param newReference 引用的新值
      * @param expectStamp标记的期望值
      * @param newStamp 标记的新值
      * @return {@code true} 如果成功
      */
public boolean compareAndSet(V   expectedReference,
                                 V   newReference,
                                 int expectedStamp,
                                 int newStamp) {
        Pair<V> current = pair;
        return
            expectedReference == current.reference &&
            expectedStamp == current.stamp &&
            ((newReference == current.reference &&
              newStamp == current.stamp) ||
             casPair(current, Pair.of(newReference, newStamp)));
    }
    
     private boolean casPair(Pair<V> cmp, Pair<V> val) {
        return UNSAFE.compareAndSwapObject(this, pairOffset, cmp, val);
    }