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

一:問題拋出java

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

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是提供原子操做的this

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

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

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

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

/** 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問題,使用傳統的互斥同步可能迴避原子類更加高效。

相關文章
相關標籤/搜索