1. 程式人生 > 程式設計 >java CAS原理

java CAS原理

一、CAS簡介

1. CAS是什麼?

     CAS全稱是Compare and Swap,即比較並交換,是通過原子指令來實現多執行緒的同步功能,將獲取儲存在記憶體地址的原值和指定的記憶體地址進行比較,只有當他們相等時,交換指定的預期值和記憶體中的值,這個操作是原子操作,若不相等,則重新獲取儲存在記憶體地址的原值。

2. CAS的流程

     CAS是一種無鎖演演算法,有3個關鍵運算元,記憶體地址,舊的記憶體中預期值,要更新的新值,當記憶體值和舊的記憶體中預期值相等時,將記憶體中的值更新為新值。

3.樂觀鎖與悲觀鎖

     CAS屬於樂觀鎖,樂觀鎖就是每次不加鎖而是假設沒有衝突而去完成某項操作,如果因為衝突失敗就重試,直到成功為止。
     synchronized是悲觀鎖,被一個執行緒拿到鎖之後,其他執行緒必須等待該執行緒釋放鎖,效能較差

二、AtomicInteger程式碼演示

     在java中,a++不是原子操作,一個簡單的a++操作涉及到三個操作,獲取變數a的記憶體值,將變數a+1,將新值寫入記憶體,這裡涉及到了兩次記憶體訪問,如果在多執行緒環境下,那麼會出現併發安全問題。
     AtomicInteger是一個原子操作類,內部採用的就是CAS無鎖演演算法。 這裡我們分析一下它的內部實現。

AtomicInteger atomicInteger = new AtomicInteger(0);
atomicInteger.getAndSet(1);  
複製程式碼

     這裡的靜態程式碼塊AtomicInteger物件初始化之前就執行,獲取AtomicInteger物件value欄位相對AtomicInteger物件的”起始地址”的偏移量,Java物件在記憶體中儲存的佈局可以分為三塊區域:物件頭(Header)、例項資料(Instance Data)和對齊填充(Padding),”起始地址”的偏移量即是物件頭的偏移量。

static {
    try {
        valueOffset = unsafe.objectFieldOffset
            (AtomicInteger.class.getDeclaredField("value"));
    } catch (Exception ex) { throw new Error(ex); }
}
複製程式碼
public final int getAndSet(int newValue) {
    return unsafe.getAndSetInt(this,valueOffset,newValue);
}
複製程式碼

     每次通過記憶體地址(var2)先從記憶體中獲取記憶體中原值(var5),再迴圈將記憶體中的原值(var5)與給定記憶體地址(var2)相比較,如果相等則更新指定預期值(var4),如果不相等則再重試直到成功為止,最後返回舊的記憶體原值var5。

//var1為AtomicInteger物件,var2為記憶體地址值,var4為指定的預期值
public final int getAndSetInt(Object var1,long var2,int var4) {
    int var5;
    do {
	//unsafe.getIntVolatile呼叫本地方法獲取記憶體中值
        var5 = this.getIntVolatile(var1,var2);
    } while(!this.compareAndSwapInt(var1,var2,var5,var4));

    return var5;
}
複製程式碼

三、弊端

1. ABA問題

     CAS在操作的時候會檢查變數的值是否被更改過,如果沒有則更新值,但是帶來一個問題,最開始的值是A,接著變成B,最後又變成了A。經過檢查這個值確實沒有修改過,因為最後的值還是A,但是實際上這個值確實已經被修改過了。為瞭解決這個問題,在每次進行操作的時候加上一個版本號,每次操作的就是兩個值,一個版本號和某個值,A——>B——>A問題就變成了1A——>2B——>3A。在jdk中提供了AtomicStampedReference類解決ABA問題,用Pair這個內部類實現,包含兩個屬性,分別代表版本號和引用,在compareAndSet中先對當前引用進行檢查,再對版本號標誌進行檢查,只有全部相等才更新值。

2. 只能保證一個共享變數的原子操作

     多個共享變數操作時,迴圈CAS就無法保證操作的原子性,這個時候就可以用鎖。從java1.5開始,JDK提供了AtomicReference類來保證引用物件之間的原子性,就可以把多個變數放在一個物件裡來進行CAS操作。

3. 迴圈時間長CPU開銷較大

     在併發量比較高的情況下,如果許多執行緒反覆嘗試更新某一個變數,卻又一直更新不成功,迴圈往復,會給CPU帶來很大的壓力。

作者:陶章好
連結:juejin.im/post/5d63ea… 來源:掘金
著作權歸作者所有。商業轉載請聯絡作者獲得授權,非商業轉載請註明出處。