CAS淺析

一、CAS

  • 定義:

比較和交換(Compare And Swap),它是一條CPU併發原語,是用於多線程同步的原子指令

  • 作用:

判斷內存中某個位置的值是否爲預期值,如果是則更改爲新的值,這個過程是原子性的

  • 思想:

樂觀鎖的思想(CAS本身不加鎖),當多個線程使用CAS操作一個變量,同一時間只有一個能更新成功,其它的失敗,但失敗的線程不會被掛起,允許再次嘗試(自旋)

  • 實現:

CAS操作的執行依賴於Unsafe類的方法

  • 優點:

1)效率高,非阻塞(線程不會掛起,自旋),、

  • 缺點:

1)循環時間長,cpu開銷大
2)只能保證一個變量的共享操作
3)ABA問題

  • 擴展-原語:

CAS是一種系統原語,屬於操作系統用語範疇,由若干條指令組成,用於完成某個功能,原語的執行必須是連續的,在執行過程中不允許被中斷,所以CAS操作是一個原子性操作


二、Unsafe類

  • 作用:
    Unsafe類存在於sun.misc包中,其內部方法操作可以像C的指針一樣直接操作內存,Unsafe類中的所有方法都是native修飾的,也就是說Unsafe類中的方法都直接調用操作系統底層資源執行相應任務
  • 1. 源碼中相關CAS操作
    在Java中無鎖操作CAS基於以下3個方法實現
    public final native boolean compareAndSwapObject(Object var1, long var2, Object var4, Object var5);

    public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);

    public final native boolean compareAndSwapLong(Object var1, long var2, long var4, long var6);
    
  • 2. 線程掛起與恢復
    Java對線程的掛起操作被封裝在 LockSupport類中,LockSupport類中有各種版本pack方法,其底層實現最終還是使用Unsafe.park()方法和Unsafe.unpark()方法
//線程調用該方法,線程將一直阻塞直到超時,或者是中斷條件出現。  
public native void park(boolean isAbsolute, long time);  
 
//終止掛起的線程,恢復正常
public native void unpark(Object thread); 

三、原子操作類
CAS在Java中的應用,即併發包中的原子操作類(Atomic系列),從JDK 1.5開始提供了java.util.concurrent.atomic包,在該包中提供了許多基於CAS實現的原子操作類

  • 1. 原子更新類
  • AtomicBoolean:原子更新布爾類型
  • AtomicInteger:原子更新整型
  • AtomicLong:原子更新長整型
  • 2. AtomicInteger源碼
    AtomicInteger主要是針對int類型的數據執行原子操作,它提供了原子自增方法、原子自減方法以及原子賦值方法等
public class AtomicInteger extends Number implements java.io.Serializable {
    private static final long serialVersionUID = 6214790243416807050L;
 
    // 獲取指針類Unsafe
    private static final Unsafe unsafe = Unsafe.getUnsafe();
 
    //下述變量value在AtomicInteger實例對象內的內存偏移量
    private static final long valueOffset;
 
    static {
        try {
           //通過unsafe類的objectFieldOffset()方法,獲取value變量在對象內存中的偏移
           //通過該偏移量valueOffset,unsafe類的內部方法可以獲取到變量value對其進行取值或賦值操作
            valueOffset = unsafe.objectFieldOffset
                (AtomicInteger.class.getDeclaredField("value"));
        } catch (Exception ex) { throw new Error(ex); }
    }
   //當前AtomicInteger封裝的int變量value
    private volatile int value;
 
    public AtomicInteger(int initialValue) {
        value = initialValue;
    }
    public AtomicInteger() {
    }
   //獲取當前最新值,
    public final int get() {
        return value;
    }
    //設置當前值,具備volatile效果,方法用final修飾是爲了更進一步的保證線程安全。
    public final void set(int newValue) {
        value = newValue;
    }
    //最終會設置成newValue,使用該方法後可能導致其他線程在之後的一小段時間內可以獲取到舊值,有點類似於延遲加載
    public final void lazySet(int newValue) {
        unsafe.putOrderedInt(this, valueOffset, newValue);
    }
   //設置新值並獲取舊值,底層調用的是CAS操作即unsafe.compareAndSwapInt()方法
    public final int getAndSet(int newValue) {
        return unsafe.getAndSetInt(this, valueOffset, newValue);
    }
   //如果當前值爲expect,則設置爲update(當前值指的是value變量)
    public final boolean compareAndSet(int expect, int update) {
        return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
    }
    //當前值加1返回舊值,底層CAS操作
    public final int getAndIncrement() {
        return unsafe.getAndAddInt(this, valueOffset, 1);
    }
    //當前值減1,返回舊值,底層CAS操作
    public final int getAndDecrement() {
        return unsafe.getAndAddInt(this, valueOffset, -1);
    }
   //當前值增加delta,返回舊值,底層CAS操作
    public final int getAndAdd(int delta) {
        return unsafe.getAndAddInt(this, valueOffset, delta);
    }
    //當前值加1,返回新值,底層CAS操作
    public final int incrementAndGet() {
        return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
    }
    //當前值減1,返回新值,底層CAS操作
    public final int decrementAndGet() {
        return unsafe.getAndAddInt(this, valueOffset, -1) - 1;
    }
   //當前值增加delta,返回新值,底層CAS操作
    public final int addAndGet(int delta) {
        return unsafe.getAndAddInt(this, valueOffset, delta) + delta;
    }
   //省略一些不常用的方法....
}

  • 3.Unsafe類中 getAndAddInt()方法
    AtomicInteger類中所有自增或自減的方法都間接調用Unsafe類中的getAndAddInt()方法實現了CAS操作,從而保證了線程安全
#var1:AtomicInteger對象本身
#var2:該對象的引用地址
#var4:需要變動的數量
#var5:用var1、var2找出的主內存中的真實值
#用對象當前值和var5進行比較
#如果相同,更新var5+var4並且返回true
#如果不同,繼續取值然後再比較,直到更新完成
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;
}

getAndAddInt通過一個while循環不斷的重試更新要設置的值,直到成功爲止,調用的是Unsafe類中的compareAndSwapInt方法,是一個CAS操作方法

  • 底層原理:
    CAS底層使用JNI調用C代碼實現的,如果你有Hotspot源碼,那麼在Unsafe.cpp 裏可以找到它的實現:
static JNINativeMethod methods_15[] = {
    //省略一堆代碼...
    {CC"compareAndSwapInt",  CC"("OBJ"J""I""I"")Z",      FN_PTR(Unsafe_CompareAndSwapInt)},
    {CC"compareAndSwapLong", CC"("OBJ"J""J""J"")Z",      FN_PTR(Unsafe_CompareAndSwapLong)},
    //省略一堆代碼...
};

我們可以看到compareAndSwapInt實現是在Unsafe_CompareAndSwapInt裏面,再深入到Unsafe_CompareAndSwapInt:

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

p是取出的對象,addr是p中offset處的地址,最後調用了Atomic::cmpxchg(x, addr, e), 其中參數x是即將更新的值,參數e是原內存的值

inline jint     Atomic::cmpxchg    (jint     exchange_value, volatile jint*     dest, jint     compare_value) {
  int mp = os::is_MP();
  __asm__ volatile (LOCK_IF_MP(%4) "cmpxchgl %1,(%3)"
                    : "=a" (exchange_value)
                    : "r" (exchange_value), "a" (compare_value), "r" (dest), "r" (mp)
                    : "cc", "memory");
  return exchange_value;
}

四、CAS問題

  • 1.ABA問題
    當第一個線程執行CAS(V,E,U)操作,在獲取到當前變量V,準備修改爲新值U前,另外兩個線程已連續修改了兩次變量V的值,使得該值又恢復爲舊值,這樣的話,我們就無法正確判斷這個變量是否已被修改過
    在這裏插入圖片描述
  • 解決ABA問題
    目前在JDK的atomic包裏提供了一個類AtomicStampedReference(帶版本號的原子引用類)來解決ABA問題。這個類的compareAndSet方法作用是首先檢查當前引用是否等於預期引用,並且當前標誌是否等於預期標誌,如果全部相等,則以原子方式將該引用和該標誌的值設置爲給定的更新值。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章