java併發編程的藝術(4)CAS筆記

CAS(Compare And Swap )
在java裏面的鎖,我們經常會談起一些樂觀鎖和悲觀鎖。
其實兩者的區別主要就在於對數據加鎖的時候是採取樂觀的策略還是悲觀的策略罷了。
但是由於每一次加鎖的時候,實際上都會在訪問共享資源時發生衝突,線程需要進行等待鎖的解開。而cas技術主要是一種無鎖的機制,採用cas技術可以保證線程之間的安全性。
在常說的cas裏面有我們常說的幾個關鍵概念:
執行函數:CAS(V,E,N)
V我們要進行更新的變量
E表示預期值
N表示新值
這三個要點構成了整個CAS的運作流程:
當我們要進行更新的時候,如果V==E則表示當前值可以修改,沒有被其他的線程篡改,將N賦值爲V。如果是發現V!=E的話,說明再次過程中成功有其他線程對原來的值進行了修改。那麼就會發生重新讀取該值進行修改。

Unsafe類

這個類是java裏面比較少有人所知道的一個類,因爲裏面的方法大多都是native修飾的函數,主要是提供類似於C的指針那樣進行內存操作的功能。例如其中的allocateMemory,reallocateMemory,
freeMemory,setMemory,getAddress這些都是我們用於設置和獲取內存地址的函數接口。
當然也可以指定內存進行賦值操作:putLong
指定內存獲取數值操作:getLong

Unsafe裏面和cas相關的一些函數接口主要爲以下幾個:

第一個參數o爲給定對象,offset爲對象內存的偏移量,通過這個偏移量迅速定位字段並設置或獲取該字段的值,
expected表示期望值,x表示要設置的值,下面3個方法都通過CAS原子指令執行操作。

public final native boolean compareAndSwapObject(Object o, long offset,Object expected, Object x);                                                                                                  
 
public final native boolean compareAndSwapInt(Object o, long offset,int expected,int x);
 
public final native boolean compareAndSwapLong(Object o, long offset,long expected,long x);

那麼除了以上三種類型之外,其他的數據類型可以通過類型轉換來進行cas操作,因此核心部分還是上述的這三種方案。

incrementAndGet的源碼分析:

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;
}

jdk8之後的compareAndSwapInt主要是採用了while循環的方式,不斷的進行預期值循環判斷。

cas裏面解決aba問題的方案策略:
什麼是aba問題?

假設有兩個線程T1、T2,同時操作隊形O:

  • T1讀取數據得到A, 此時T1被掛起,T2執行。
  • T2 讀取數據也是A,並且執行,將其修改爲B
  • T2 繼續操作,在此以同樣的操作將B又改爲A
  • 此時T2被掛起,T1繼續執行
  • T1 獲取到數據此時還是A(修改過之後)
  • T1 繼續將數據修改。

雖然從結果上看,並沒有問題但是從過程上看並不是預期的,並且是存在安全隱患的。

爲了解決ABA問題,jdk裏面提供了兩種原子類,分別是AtomicStampedReference和AtomicMarkableReference。通過在操控對象的時候,添加相應的時間戳或者版本號來進行辨別。
核心的源碼主要是採用一個Pair的結構類。

**AtomicStampedReference中的Pair:**
     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);
            }
        }

**AtomicMarkableReference中的Pair**
       private static class Pair<T> {
            final T reference;
            final boolean mark;
            private Pair(T reference, boolean mark) {
                this.reference = reference;
                this.mark = mark;
            }
            static <T> Pair<T> of(T reference, boolean mark) {
                return new Pair<T>(reference, mark);
            }
        }

總結來說,ABA問題的解決思路就是通過採用添加一個時間戳或者版本號方式來進行判斷。鎖每次在執行數據的修改操作時,都會帶上一個版本號,一旦版本號和數據的版本號一致就可以執行修改操作並對版本號執行+1操作,否則就執行失敗。

參考博文:https://blog.csdn.net/crazyhsf/article/details/81229497

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章