CAS底层源码

CAS底层源码_第1张图片

CAS 全称为Compare And Swap 翻译为比较交换,作用是让CPU比较两个值是否相等,然后原子的更新某个位置的值,实现方式基于硬件平台的汇编指令,在intel的CPU中,使用的是cmpxchg指令,就是说CAS是靠硬件实现的,从而在硬件层面提升效率。

## CSA 原理

利用CPU的CAS指令,同时借助JNI来完成Java的非阻塞算法。
CAS操作是原子性的,所以多线程并发使用CAS更新数据时,可以不使用锁,JDK中大量使用CAS来更新数据而防止加锁来保持原子更新。
CAS操作包含三个操作数:**内存偏移量位置,预期原值,新值**。如果内存位置的值与预期值相同,那么处理器会自动将该位置值更新为新值,否则处理器不做处理。

## 源码分析

JUC包下面的类大部分都用了CAS实现原子性操作

### AtomicInteger 源码解析

```
// 使用 unsafe 类的原子操作方式
private static final Unsafe unsafe = Unsafe.getUnsafe();
private static final long valueOffset;

static {
    try {
        //计算变量 value 在类对象中的偏移量
        valueOffset = unsafe.objectFieldOffset(AtomicInteger.class.getDeclaredField("value"));
    } catch (Exception ex) { throw new Error(ex); }
}
```

`valueOffset` 字段表示 ` "value"` 内存位置,在 `compareAndSwap` 方法 ,第二个参数会用到.

`偏移量`计算方式=

```
//方法相当于原子性的 ++i
public final int getAndIncrement() {
    //三个参数,1、当前的实例 2、value实例变量的偏移量 3、递增的值。
    return unsafe.getAndAddInt(this, valueOffset, 1);
}
//方法相当于原子性的 --i
public final int getAndDecrement() {
    //三个参数,1、当前的实例 2、value实例变量的偏移量 3、递减的值。
    return unsafe.getAndAddInt(this, valueOffset, -1);
}
```

`Unsafe` 调用C 语言可以通过偏移量对变量进行操作
[OpenJDK源码地址](https://download.java.net/openjdk/jdk8/promoted/b132/openjdk-8-src-b132-03_mar_2014.zip/)
目录:`openjdk\jdk\src\share\classes\sun\misc\Unsafe.java`

看代码可知 Unsafe使用了[单例模式](https://www.runoob.com/design-pattern/singleton-pattern.html)并提供getUnsafe()方法获取。

```

//获取Unsafe实例静态方法
@CallerSensitive
public static Unsafe getUnsafe() {
    Class caller = Reflection.getCallerClass();
    if (!VM.isSystemDomainLoader(caller.getClassLoader()))
        throw new SecurityException("Unsafe");
    return theUnsafe;
}

```

```
 /**
     * Returns true if the given class loader is in the system domain
     * in which all permissions are granted.
     */
    public static boolean isSystemDomainLoader(ClassLoader loader) {
        return loader == null;
    }
```

只有主类[加载器]()加载的类才能调用这个方法
[Unsafe的提供 的方法]([https://www.cnblogs.com/pkufork/p/java_unsafe.html](https://www.cnblogs.com/pkufork/p/java_unsafe.html))
我们来看看getAndAddInt方法

```
/**
     * Atomically adds the given value to the current value of a field
     * or array element within the given object o
     * at the given offset.
     *
     * @param o object/array to update the field/element in
     * @param offset field/element offset
     * @param delta the value to add
     * @return the previous value
     * @since 1.8
     */
    public final int getAndAddInt(Object o, long offset, int delta) {
        int v;
        do {
        //通过对象以及偏移量位置获取值
            v = getIntVolatile(o, offset);
        } while (!compareAndSwapInt(o, offset, v, v + delta));
        return v;
    }
```

```
/**
     * Atomically update Java variable to x if it is currently
     * holding expected.
     * @return true if successful
     */
    public final native boolean compareAndSwapInt(Object o, long offset,
                                                  int expected,
                                                  int x);
```

利用 Unsafe 类的 JNI compareAndSwapInt 方法实现,使用CAS实现一个原子操作更新.

## [Unsafe.app深度分析](https://segmentfault.com/a/1190000015881923)

# CAS的ABA问题及其解决方案

假设这样一种场景,当第一个线程执行CAS(V,E,U)操作。在获取到当前变量V,准备修改为新值U前,另外两个线程已连续修改了两次变量V的值,使得该值又恢复为旧值,这样的话,我们就无法正确判断这个变量是否已被修改过,如下图:

![](https://segmentfault.com/img/remote/1460000015881927?w=491&h=247)

这就是典型的CAS的ABA问题,一般情况这种情况发现的概率比较小,可能发生了也不会造成什么问题,比如说我们对某个做加减法,不关心数字的过程,那么发生ABA问题也没啥关系。但是在某些情况下还是需要防止的,那么该如何解决呢?在Java中解决ABA问题,我们可以使用以下原子类

**AtomicStampedReference类**

AtomicStampedReference原子类是一个带有时间戳的对象引用,在每次修改后,AtomicStampedReference不仅会设置新值而且还会记录更改的时间。当AtomicStampedReference设置对象值时,对象值以及时间戳都必须满足期望值才能写入成功,这也就解决了反复读写时,无法预知值是否已被修改的窘境

底层实现为: 通过Pair私有内部类存储数据和时间戳, 并构造volatile修饰的私有实例

接着看 `java.util.concurrent.atomic.AtomicStampedReference`类的compareAndSet()方法的实现:

```
private static class Pair {
    final T reference;
    final int stamp;
  
    //最好不要重复的一个数据,决定数据是否能设置成功,时间戳会重复
    private Pair(T reference, int stamp) {
        this.reference = reference;
        this.stamp = stamp;
    }
    static Pair of(T reference, int stamp) {
        return new Pair(reference, stamp);
    }
}
```

同时对当前数据和当前时间进行比较,只有两者都相等是才会执行casPair()方法,

单从该方法的名称就可知是一个CAS方法,最终调用的还是 `Unsafe`类中的 `compareAndSwapObject`方法

到这我们就很清晰 `AtomicStampedReference`的内部实现思想了,

通过一个键值对 `Pair`存储数据和时间戳,在更新时对数据和时间戳进行比较,

只有两者都符合预期才会调用 `Unsafe`的 `compareAndSwapObject`方法执行数值和时间戳替换,也就避免了ABA的问题。

```
/**
 * 原子更新带有版本号的引用类型。
 * 该类将整数值与引用关联起来,可用于原子的更数据和数据的版本号。
 * 可以解决使用CAS进行原子更新时,可能出现的ABA问题。
 */
public class AtomicStampedReference {
    //静态内部类Pair将对应的引用类型和版本号stamp作为它的成员
    private static class Pair {
    
        //最好不要重复的一个数据,决定数据是否能设置成功,建议时间戳
        final T reference;
        final int stamp;
        private Pair(T reference, int stamp) {
            this.reference = reference;
            this.stamp = stamp;
        }
    
        //根据reference和stamp来生成一个Pair的实例
        static Pair of(T reference, int stamp) {
            return new Pair(reference, stamp);
        }
    }
  
    //作为一个整体的pair变量被volatile修饰
    private volatile Pair pair;
 
    //构造方法,参数分别是初始引用变量的值和初始版本号
    public AtomicStampedReference(V initialRef, int initialStamp) {
        pair = Pair.of(initialRef, initialStamp);
    }
  
    ....
  
    private static final sun.misc.Unsafe UNSAFE = sun.misc.Unsafe.getUnsafe();
  
    private static final long pairOffset = objectFieldOffset(UNSAFE, "pair", AtomicStampedReference.class);
 
    //获取pair成员的偏移地址
    static long objectFieldOffset(sun.misc.Unsafe UNSAFE,
                                  String field, Class klazz) {
        try {
            return UNSAFE.objectFieldOffset(klazz.getDeclaredField(field));
        } catch (NoSuchFieldException e) {
            NoSuchFieldError error = new NoSuchFieldError(field);
            error.initCause(e);
            throw error;
        }
    }
}
```

```


/**
 * @param 期望(老的)引用
 * @param       (新的)引用数据
 * @param 期望(老的)标志stamp(时间戳)值
 * @param       (新的)标志stamp(时间戳)值
 * @return 是否成功
 */
public boolean compareAndSet(V expectedReference,V   newReference,int expectedStamp,int newStamp) {
     
    Pair current = pair;
    return
        // 期望(老的)引用 == 当前引用
        expectedReference == current.reference &&
        // 期望(老的)标志stamp(时间戳)值 == 当前标志stamp(时间戳)值
        expectedStamp == current.stamp &&
    
        // (新的)引用数据 == 当前引用数据 并且 (新的)标志stamp(时间戳)值 ==当前标志stamp(时间戳)值
        ((newReference == current.reference && newStamp == current.stamp) ||
          #原子更新值
         casPair(current, Pair.of(newReference, newStamp)));
     
}
 
 //当引用类型的值与期望的一致的时候,原子的更改版本号为新的值。该方法只修改版本号,不修改引用变量的值,成功返回true
public boolean attemptStamp(V expectedReference, int newStamp) {
    Pair current = pair;
    return
        expectedReference == current.reference &&
        (newStamp == current.stamp ||
         casPair(current, Pair.of(expectedReference, newStamp)));
}

/**
 * CAS真正实现方法
 */
private boolean casPair(Pair cmp, Pair val) {
        return UNSAFE.compareAndSwapObject(this, pairOffset, cmp, val);
}
```

期望 Pair cmp(A) == 当前内存存偏移量位置 Pair(V),就更新值 Pair val(B)成功返回true 否则 false

```
public static void main(String[] args) {
    AtomicStampedReference num = new AtomicStampedReference(1, 0);

    Integer i = num.getReference();
    int stamped = num.getStamp();

    if (num.compareAndSet(i, i + 1, stamped, stamped + 1)) {
        System.out.println("测试成功");
    }
}
```

通过以上原子更新方法,可见 AtomicStampedReference就是利用了Unsafe的CAS方法+Volatile关键字对存储实际的引用变量和int的版本号的Pair实例进行更新。

### ConcurrentHashMap 在 JDK 1.8 的版本中,也调整为 CAS + `synchronized`

你可能感兴趣的:(CAS,底层CAS源码分析)