天天看點

AtomicInteger如何實作原子操作

在 java 中可以通過鎖和循環 CAS 的方式來實作原子操作。AtomicInteger是通過循環 CAS 的方式來實作。

那麼循環 CAS 是怎麼保證原子性?

AtomicInteger atomicI = new AtomicInteger(0);
atomicI.incrementAndGet();
           

incrementAndGet的如下源碼:

public final int incrementAndGet() {
        return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
    }
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;
    }
        public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);
        public native int getIntVolatile(Object var1, long var2);

           

Unsafe 類提供 CAS 操作,CAS操作是本地方法(native),而且是不可改變(final)的。偏移量是幹嘛用的呢?

public class AtomicInteger extends Number implements java.io.Serializable {
  // setup to use Unsafe.compareAndSwapInt for updates
    private static final Unsafe unsafe = Unsafe.getUnsafe();
    private static final long valueOffset;

    static {
        try {
            valueOffset = unsafe.objectFieldOffset
                (AtomicInteger.class.getDeclaredField("value"));
        } catch (Exception ex) { throw new Error(ex); }
    }

    private volatile int value;
}
           

AtomicInteger類中定義了一個value屬性,并通過unsafe.objectFieldOffset方法擷取到了這個屬性在主存中設定的偏移量valueOffset。接着就可以在getAndIncrement方法中直接使用unsafe.getAndAddInt的方式,通過偏移量valueOffset将value屬性的值加“1”。var5即為取到的變量,在以上代碼中,getAndIncrement方法内部會不停的循環,比較value值和expect值,如果不相等,重新擷取valuez,直到expect=value = var5,更新value值為var5 + var4,此時unsafe.compareAndSwapInt方法執行成功。那麼compareAndSwapInt(Object var1, long var2, int var4, int var5)方法又是如何保證原子性的呢?

compareAndSet利用JNI來完成CPU指令的原子操作。

反過來,為什麼不直接通過JNI完成CPU指令的方式直接實作incrementAndGet()呢

因為CPU指令隻能實作簡單的操作,Inter 處理器提供了很多 LOCK 字首的指令來實作。比如位測試和修改指令 BTS,BTR,BTC,交換指令 XADD,CMPXCHG 和其他一些操作數和邏輯指令,比如 ADD(加),OR(或)等,被這些指令操作的記憶體區域就會加鎖,導緻其他處理器不能同時通路它。 

為什麼要自旋循環呢?

為了保證讀取到的value是最新的,并且循環執行直到成功。即使被其它線程修改了value值,仍可以擷取到修改後的最新值,并且不斷比較,直至修改成功。

CPU如何實作原子操作?

32 位 IA-32 處理器使用基于對緩存加鎖或總線加鎖的方式來實作多處理器之間的原子操作。

1 處理器自動保證基本記憶體操作的原子性

首先處理器會自動保證基本的記憶體操作的原子性。處理器保證從系統記憶體當中讀取或者寫入一個位元組是原子的,意思是當一個處理器讀取一個位元組時,其他處理器不能通路這個位元組的記憶體位址。奔騰 6 和最新的處理器能自動保證單處理器對同一個緩存行裡進行 16/32/64 位的操作是原子的,但是複雜的記憶體操作處理器不能自動保證其原子性,比如跨總線寬度,跨多個緩存行,跨頁表的通路。但是處理器提供總線鎖定和緩存鎖定兩個機制來保證複雜記憶體操作的原子性。

2 使用總線鎖保證原子性

第一個機制是通過總線鎖保證原子性。如果多個處理器同時對共享變量進行讀改寫(i++ 就是經典的讀改寫操作)操作,那麼共享變量就會被多個處理器同時進行操作,這樣讀改寫操作就不是原子的,操作完之後共享變量的值會和期望的不一緻,舉個例子:如果 i=1, 我們進行兩次 i++ 操作,我們期望的結果是 3,但是有可能結果是 2。如下圖

AtomicInteger如何實作原子操作

原因是有可能多個處理器同時從各自的緩存中讀取變量 i,分别進行加一操作,然後分别寫入系統記憶體當中。那麼想要保證讀改寫共享變量的操作是原子的,就必須保證 CPU1 讀改寫共享變量的時候,CPU2 不能操作緩存了該共享變量記憶體位址的緩存。

處理器使用總線鎖就是來解決這個問題的。所謂總線鎖就是使用處理器提供的一個 LOCK#信号,當一個處理器在總線上輸出此信号時,其他處理器的請求将被阻塞住, 那麼該處理器可以獨占使用共享記憶體。

3 使用緩存鎖保證原子性

第二個機制是通過緩存鎖定保證原子性。在同一時刻我們隻需保證對某個記憶體位址的操作是原子性即可,但總線鎖定把 CPU 和記憶體之間通信鎖住了,這使得鎖定期間,其他處理器不能操作其他記憶體位址的資料,是以總線鎖定的開銷比較大,最近的處理器在某些場合下使用緩存鎖定代替總線鎖定來進行優化。

頻繁使用的記憶體會緩存在處理器的 L1,L2 和 L3 高速緩存裡,那麼原子操作就可以直接在處理器内部緩存中進行,并不需要聲明總線鎖,在奔騰 6 和最近的處理器中可以使用“緩存鎖定”的方式來實作複雜的原子性。所謂“緩存鎖定”就是如果緩存在處理器緩存行中記憶體區域在 LOCK 操作期間被鎖定,當它執行鎖操作回寫記憶體時,處理器不在總線上聲言 LOCK#信号,而是修改内部的記憶體位址,并允許它的緩存一緻性機制來保證操作的原子性,因為緩存一緻性機制會阻止同時修改被兩個以上處理器緩存的記憶體區域資料,當其他處理器回寫已被鎖定的緩存行的資料時會起緩存行無效,在例 1 中,當 CPU1 修改緩存行中的 i 時使用緩存鎖定,那麼 CPU2 就不能同時緩存了 i 的緩存行。

但是有兩種情況下處理器不會使用緩存鎖定。第一種情況是:當操作的資料不能被緩存在處理器内部,或操作的資料跨多個緩存行(cache line),則處理器會調用總線鎖定。第二種情況是:有些處理器不支援緩存鎖定。對于 Inter486 和奔騰處理器, 就算鎖定的記憶體區域在處理器的緩存行中也會調用總線鎖定。

緩存一緻性機制怎麼阻止同時被兩個以上處理器修改呢?

在多處理器下,為了保證各個處理器的緩存是一緻的,就會實作緩存一緻性協定,每個處理器通過嗅探在總線上傳播的資料來檢查自己緩存的值是不是過期了,當處理器發現自己緩存行對應的記憶體位址被修改,就會将目前處理器的緩存行設定成無效狀态,當處理器要對這個資料進行修改操作的時候,會強制重新從系統記憶體裡把資料讀到處理器緩存裡。一個處理器的緩存回寫到記憶體會導緻其他處理器的緩存無效。

value為什麼要用valatile修飾呢?

Java 代碼:
instance = new Singleton();//instance 是 volatile 變量
彙編代碼:
0x01a3de1d: movb $0x0,0x1104800(%esi);
0x01a3de24: lock addl $0x0,(%esp);
           

為了保證可見性。即實作緩沖一緻性機制。如果一個字段被聲明成 volatile,java 線程記憶體模型確定所有線程看到這個變量的值是一緻。有 volatile 變量修飾的共享變量進行寫操作的時候會多第二行彙編代碼,通過查 IA-32 架構軟體開發者手冊可知,lock 字首的指令在多核處理器下會引發了兩件事情。

将目前處理器緩存行的資料會寫回到系統記憶體。
這個寫回記憶體的操作會引起在其他 CPU 裡緩存了該記憶體位址的資料無效。
           

循環 CAS 的缺點?

1 ABA 問題。

2 循環時間長開銷大。

3 隻能保證一個共享變量的原子操作。

參考文檔:

https://www.jianshu.com/p/450925729f72

https://www.jianshu.com/p/df0585b61773

https://www.infoq.cn/article/ftf-java-volatile

https://www.infoq.cn/article/atomic-operation