AVA中CAS-ABA的問題解決方案AtomicStampedReference

瞭解CAS(Compare-And-Swap)

CAS即對比交換,它在保證數據原子性的前提下儘量的減小了鎖的使用,不少編程語言或者系統實現上都大量的使用了CAS。編程

JAVA中CAS的實現

JAVA中的cas主要使用的是Unsafe方法,Unsafe的CAS操做主要是基於硬件平臺的彙編指令,目前的處理器基本都支持CAS,只不過不一樣的廠家的實現不同罷了。app

Unsafe提供了三個方法用於CAS操做,分別是編程語言

public final native boolean compareAndSwapObject(Object value, long valueOffset, Object expect, Object update);

public final native boolean compareAndSwapInt(Object value, long valueOffset, int expect, int update);

public final native boolean compareAndSwapLong(Object value, long valueOffset, long expect, long update);
  • value 表示 須要操做的對象
  • valueOffset 表示 對象(value)的地址的偏移量(經過Unsafe.objectFieldOffset(Field valueField)獲取)
  • expect 表示更新時value的期待值
  • update 表示將要更新的值

具體過程爲每次在執行CAS操做時,線程會根據valueOffset去內存中獲取當前值去跟expect的值作對好比果一致則修改並返回true,若是不一致說明有別的線程也在修改此對象的值,則返回falseoop

Unsafe類中compareAndSwapInt的具體實現:this

UNSAFE_ENTRY(jboolean, Unsafe_CompareAndSwapInt(JNIEnv *env, jobject unsafe, jobject obj, jlong offset, jint e, jint x))
  UnsafeWrapper("Unsafe_CompareAndSwapInt");
  oop p = JNIHandles::resolve(obj);
  jint* addr = (jint *) index_oop_from_field_offset_long(p, offset);
  return (jint)(Atomic::cmpxchg(x, addr, e)) == e;
UNSAFE_END

ABA問題

線程1準備用CAS修改變量值A,在此以前,其它線程將變量的值由A替換爲B,又由B替換爲A,而後線程1執行CAS時發現變量的值仍然爲A,因此CAS成功。但實際上這時的現場已經和最初不一樣了。atom


aba.pngspa

例子

public static AtomicInteger a = new AtomicInteger(1);
public static void main(String[] args){
    Thread main = new Thread(() -> {
        System.out.println("操做線程" + Thread.currentThread() +",初始值 = " + a);  //定義變量 a = 1
        try {
            Thread.sleep(1000);  //等待1秒 ,以便讓干擾線程執行
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        boolean isCASSuccess = a.compareAndSet(1,2); // CAS操做
        System.out.println("操做線程" + Thread.currentThread() +",CAS操做結果: " + isCASSuccess);
    },"主操做線程");

    Thread other = new Thread(() -> {
        a.incrementAndGet(); // a 加 1, a + 1 = 1 + 1 = 2
        System.out.println("操做線程" + Thread.currentThread() +",【increment】 ,值 = "+ a);
        a.decrementAndGet(); // a 減 1, a - 1 = 2 - 1 = 1
        System.out.println("操做線程" + Thread.currentThread() +",【decrement】 ,值 = "+ a);
    },"干擾線程");

    main.start();
    other.start();
}
// 輸出
> 操做線程Thread[主操做線程,5,main],初始值 = 1
> 操做線程Thread[干擾線程,5,main],【increment】 ,值 = 2
> 操做線程Thread[干擾線程,5,main],【decrement】 ,值 = 1
> 操做線程Thread[主操做線程,5,main],CAS操做結果: true

解決ABA方案

思路

解決ABA最簡單的方案就是給值加一個修改版本號,每次值變化,都會修改它版本號,CAS操做時都對比此版本號。線程

aba_2.pngcode

JAVA中ABA中解決方案(AtomicStampedReference)

AtomicStampedReference主要維護包含一個對象引用以及一個能夠自動更新的整數"stamp"的pair對象來解決ABA問題。對象

//關鍵代碼
public class AtomicStampedReference<V> {
    private static class Pair<T> {
        final T reference;  //維護對象引用
        final int stamp;  //用於標誌版本
        private Pair(T reference, int stamp) {
            this.reference = reference;
            this.stamp = stamp;
        }
        static <T> Pair<T> of(T reference, int stamp) {
            return new Pair<T>(reference, stamp);
        }
    }
    private volatile Pair<V> pair;
    ....
    /**
      * expectedReference :更新以前的原始值
      * newReference : 將要更新的新值
      * expectedStamp : 期待更新的標誌版本
      * newStamp : 將要更新的標誌版本
      */
    public boolean compareAndSet(V   expectedReference,
                                 V   newReference,
                                 int expectedStamp,
                                 int newStamp) {
        Pair<V> current = pair; //獲取當前pair
        return
            expectedReference == current.reference && //原始值等於當前pair的值引用,說明值未變化
            expectedStamp == current.stamp && // 原始標記版本等於當前pair的標記版本,說明標記未變化
            ((newReference == current.reference &&
              newStamp == current.stamp) || // 將要更新的值和標記都沒有變化
             casPair(current, Pair.of(newReference, newStamp))); // cas 更新pair
    }
}

例子

private static AtomicStampedReference<Integer> atomicStampedRef =
        new AtomicStampedReference<>(1, 0);
public static void main(String[] args){
    Thread main = new Thread(() -> {
        System.out.println("操做線程" + Thread.currentThread() +",初始值 a = " + atomicStampedRef.getReference());
        int stamp = atomicStampedRef.getStamp(); //獲取當前標識別
        try {
            Thread.sleep(1000); //等待1秒 ,以便讓干擾線程執行
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        boolean isCASSuccess = atomicStampedRef.compareAndSet(1,2,stamp,stamp +1);  //此時expectedReference未發生改變,可是stamp已經被修改了,因此CAS失敗
        System.out.println("操做線程" + Thread.currentThread() +",CAS操做結果: " + isCASSuccess);
    },"主操做線程");

    Thread other = new Thread(() -> {
        atomicStampedRef.compareAndSet(1,2,atomicStampedRef.getStamp(),atomicStampedRef.getStamp() +1);
        System.out.println("操做線程" + Thread.currentThread() +",【increment】 ,值 = "+ atomicStampedRef.getReference());
        atomicStampedRef.compareAndSet(2,1,atomicStampedRef.getStamp(),atomicStampedRef.getStamp() +1);
        System.out.println("操做線程" + Thread.currentThread() +",【decrement】 ,值 = "+ atomicStampedRef.getReference());
    },"干擾線程");

    main.start();
    other.start();
}
// 輸出
> 操做線程Thread[干擾線程,5,main],【increment】 ,值 = 2
> 操做線程Thread[干擾線程,5,main],【decrement】 ,值 = 1
> 操做線程Thread[主操做線程,5,main],初始值 a = 1
> 操做線程Thread[主操做線程,5,main],CAS操做結果: true
相關文章
相關標籤/搜索