非阻塞式的原子性操作-CAS應用及原理


一:問題拋出

假設在出現高並發的情況下對一個整數變量做依次遞增操作,下面這兩段代碼是否會出現問題?

1.

public class IntegerTest  {
    private static Integer count = 0;
    synchronized public static void increment() {
        count++;
    }
}

2.

public class AtomicIntegerTest {
    private static AtomicInteger count = new AtomicInteger(0);
    public static void increment() {
        count.getAndIncrement();
    }
}

其實在使用Integer的時候,必須加上synchronized保證不會出現並發線程同時訪問的情況,而在AtomicInteger中卻不用加上synchronized,在這里AtomicInteger是提供原子操作的

二:先看下AtomicInteger類中屬性和初始化的一些源碼

unsafe:對應的是Unsafe類,Java無法直接訪問底層操作系統,而是通過本地(native)方法來訪問。JDK中有一個類Unsafe,它提供了硬件級別的原子操作。JDK API文檔也沒有提供任何關於這個類的方法的解釋。從描述可以了解到Unsafe提供了硬件級別的操作,比如說獲取某個屬性在內存中的位置,比如說修改對象的字段值,即使它是私有的。

value:volatile修飾的變量,內存中其他線程具有可見性。加或減都是對這個變量值進行修改。

valueOffset:這里指的就是value這個屬性在內存中的偏移量(內存中的地址,而不是值),當類被加載時先按順序初始化static變量和static塊,通過unsafe中public native long objectFieldOffset(Field paramField);

/** Returns the memory address offset of the given static field.

 * The offset is merely used as a means to access a particular field * in the other methods of this class. The value is unique to the given * field and the same value should be returned on each subsequent call. * 返回指定靜態field的內存地址偏移量,在這個類的其他方法中這個值只是被用作一個訪問 * 特定field的一個方式。這個值對於 給定的field是唯一的,並且后續對該方法的調用都應該 * 返回相同的值。 * * @param field the field whose offset should be returned. * 需要返回偏移量的field * @return the offset of the given field. * 指定field的偏移量 */ public native long objectFieldOffset(Field field);

獲取AtomicInteger類屬性value在內存中的偏移量,並將偏移量值賦給valueOffset。需要強調valueOffset代表的不是value值在內存中的位置,而是這個屬性在內存中的地址。

 

三:那么具體看下實現的源碼

1.遞增的方法:incrementAndGet()

 

getAndIncrement方法是在一個死循環里面調用compareAndSet方法,如果compareAndSet返回失敗,就會一直從頭開始循環,不會退出getAndIncrement方法,直到compareAndSet返回true。

 2.compareAndSet方法:

AtomicInteger中Unsafe實例調用compareAndSwapInt方法。

 3.compareAndSwapInt源碼:

 看到這里知道是一個本地方法的調用,比較並置換,這里利用Unsafe類的JNI方法實現,使用CAS指令,可以保證讀-改-寫是一個原子操作。compareAndSwapInt有4個參數,this - 當前AtomicInteger對象,valueOffset- value屬性在內存中的位置(需要強調的不是value值在內存中的位置),expect - 預期值,update - 新值,根據上面的CAS操作過程,當內存中的value值等於expect值時,則將內存中的value值更新為update值,並返回true,否則返回false。在這里我們有必要對Unsafe有一個簡單點的認識,從名字上來看,不安全,確實,這個類是用於執行低級別的、不安全操作的方法集合,這個類中的方法大部分是對內存的直接操作,所以不安全,但當我們使用反射、並發包時,都間接的用到了Unsafe。

 

四:並發情況處理流程:

1.首先valueOffset獲取value的偏移量,假設value=0,valueOffset=0(valueOffset其實是內存地址,便於表達-后面用valueOffset=n表示對應值的地址)。

 

 

2.線程A調用getAndIncrement方法,執行到161行,獲取current=0,next=1,准備執行compareAndSet方法

3.線程B幾乎與線程A同時調用getAndIncrement方法,執行完161行后,獲取current=0,next=1,並且先於線程A執行compareAndSet方法,此時value=1,valueOffset=1

4.線程A調用compareAndSet發現預期值(current=0)與內存中對應的值(valueOffset=1,被線程B修改)不相等,即在本線程執行期間有被修改過,則放棄此次修改,返回false。

5.線程B接着循環,通過get()獲取的值是最新的(volatile修飾的value的值會強迫線程從主內存獲取),current=1,next=2,然后發現valueOffset=current=1,修改valueOffset=2。

五:總結下AtomicInteger的getAndIncrement方法之所以比普通Integer加減更適用並發環境:

1.current代表value最新的值是因為通過get()方法會從主內存讀取(volatile,即讀取valueOffset對應的值)

2.能夠監測到get()讀取到值到cpu執行compareAndSet執行成功之前被別的線程修改成功后的並發情況。

3.上面強調被別的線程“修改成功”是因為假如出現“ABA”情況是不會被覺察的。

即:如果一個變量初次讀取的時候是A值,如果在這段期間它的值曾經被改成了B,然后又改回A,那CAS操作就會誤認為它從來沒有被修改過。這個漏洞稱為CAS操作的"ABA"問題。java.util.concurrent包為了解決這個問題,提供了一個帶有標記的原子引用類"AtomicStampedReference",它可以通過控制變量值的版本來保證CAS的正確性。大部分情況下ABA問題並不會影響程序並發的正確性,如果需要解決ABA問題,使用傳統的互斥同步可能回避原子類更加高效。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM