Java原子類實現原理分析

併發包中的原子類能夠解決相似num++這樣的複合類操做的原子性問題,相比鎖機制,使用原子類更精巧輕量,性能開銷更小,下面就一塊兒來分析下原子類的實現機理。java

悲觀的解決方案(阻塞同步)

  咱們知道,num++看似簡單的一個操做,其實是由1.讀取 2.加一 3.寫入 三步組成的,這是個複合類的操做(因此咱們以前提到過的volatile是沒法解決num++的原子性問題的),在併發環境下,若是不作任何同步處理,就會有線程安全問題。最直接的處理方式就是加鎖算法

synchronized(this){
    num++;
 }

  使用獨佔鎖機制來解決,是一種悲觀的併發策略,抱着一副「總有刁民想害朕」的態勢,每次操做數據的時候都認爲別的線程會參與競爭修改,因此直接加鎖。同一刻只能有一個線程持有鎖,那其餘線程就會阻塞。線程的掛起恢復會帶來很大的性能開銷,儘管jvm對於非競爭性的鎖的獲取和釋放作了不少優化,可是一旦有多個線程競爭鎖,頻繁的阻塞喚醒,仍是會有很大的性能開銷的。因此,使用synchronized或其餘重量級鎖來處理顯然不夠合理。安全

樂觀的解決方案(非阻塞同步)

  樂觀的解決方案,顧名思義,就是很大度樂觀,每次操做數據的時候,都認爲別的線程不會參與競爭修改,也不加鎖。若是操做成功了那最好;若是失敗了,好比中途確有別的線程進入並修改了數據(依賴於衝突檢測),也不會阻塞,能夠採起一些補償機制,通常的策略就是反覆重試。很顯然,這種思想相比簡單粗暴利用鎖來保證同步要合理的多。多線程

  鑑於併發包中的原子類其實現機理都差不太多,本章咱們就經過AtomicInteger這個原子類來進行分析。咱們先來看看對於num++這樣的操做AtomicInteger是如何保證其原子性的。併發

複製代碼
 /**
     * Atomically increments by one the current value.
     *
     * @return the updated value
     */
    public final int incrementAndGet() {
        for (;;) {
            int current = get();
            int next = current + 1;
            if (compareAndSet(current, next))
                return next;
        }
    }
複製代碼

 咱們來分析下incrementAndGet的邏輯:jvm

  1.先獲取當前的value值性能

  2.對value加一優化

  3.第三步是關鍵步驟,調用compareAndSet方法來來進行原子更新操做,這個方法的語義是:網站

    先檢查當前value是否等於current,若是相等,則意味着value沒被其餘線程修改過,更新並返回true。若是不相等,compareAndSet則會返回false,而後循環繼續嘗試更新。this

  compareAndSet調用了Unsafe類的compareAndSwapInt方法

複製代碼
/**
     * Atomically sets the value to the given updated value
     * if the current value {@code ==} the expected value.
     *
     * @param expect the expected value
     * @param update the new value
     * @return true if successful. False return indicates that
     * the actual value was not equal to the expected value.
     */
    public final boolean compareAndSet(int expect, int update) {
        return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
    }
複製代碼

  Unsafe的compareAndSwapInt是個native方法,也就是平臺相關的。它是基於CPU的CAS指令來完成的

    public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);

CAS(Compare-and-Swap)  

  CAS算法是由硬件直接支持來保證原子性的,有三個操做數:內存位置V、舊的預期值A和新值B,當且僅當V符合預期值A時,CAS用新值B原子化地更新V的值,不然,它什麼都不作。

 //CMPXCHG

Unsafe的相關用法,而下面是結合網站中的介紹和具體的AtomicInteger類來說解一下其相關的用法。

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類在域中聲明瞭這兩個私有變量unsafe和valueOffset。其中unsafe實例採用Unsafe類中靜態方法getUnsafe()獲得,可是這個方法若是咱們寫的時候調用會報錯,由於這個方法在調用時會判斷類加載器,咱們的代碼是沒有「受信任」的,而在jdk源碼中調用是沒有任何問題的;valueOffset這個是指類中相應字段在該類的偏移量,在這裏具體便是指value這個字段在AtomicInteger類的內存中相對於該類首地址的偏移量。

 

而後能夠看一個有一個靜態初始化塊,這個塊的做用便是求出value這個字段的偏移量。具體的方法使用的反射的機制獲得value的Field對象,再根據objectFieldOffset這個方法求出value這個變量內存中在該對象中的偏移量。

 

volatile關鍵字保證了在多線程中value的值是可見的,任何一個線程修改了value值,會將其當即寫回內存當中 

public final int getAndSet(int newValue) {  
for (;;) {
int current = get();  
if (compareAndSet(current, newValue)){
return current;
}  
}
   
public final boolean compareAndSet(int expect, int update) {  
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);  
}

getAndSet這個方法做用爲將value值設置爲newValue,並返回修改前的value值
在for循環中,保證每次若是compareAndSet這個方法失敗以後,能從新進行嘗試,直到成功將value值設置爲newValue。

 

compareAndSet這個方法主要調用unsafe.compareAndSwapInt這個方法,這個方法有四個參數,其中第一個參數爲須要改變的對象,第二個爲偏移量(即以前求出來的valueOffset的值),第三個參數爲期待的值,第四個爲更新後的值。整個方法的做用即爲若調用該方法時,value的值與expect這個值相等,那麼則將value修改成update這個值,並返回一個true,若是調用該方法時,value的值與expect這個值不相等,那麼不作任何操做,並範圍一個false。

 

所以之因此在getAndSet方法中調用一個for循環,即保證若是調用compareAndSet這個方法返回爲false時,能再次嘗試進行修改value的值,直到修改爲功,並返回修改前value的值。

 

整個代碼能保證在多線程時具備線程安全性,而且沒有使用java中任何鎖的機制,所依靠的即是Unsafe這個類中調用的該方法具備原子性,這個原子性的保證並非靠java自己保證,而是靠一個更底層的與操做系統相關的特性實現。

 

  CAS的ABA問題

  固然CAS也並不完美,它存在"ABA"問題,倘若一個變量初次讀取是A,在compare階段依然是A,但其實可能在此過程當中,它先被改成B,再被改回A,而CAS是沒法意識到這個問題的。CAS只關注了比較先後的值是否改變,而沒法清楚在此過程當中變量的變動明細,這就是所謂的ABA漏洞。 

http://ifeve.com/sun-misc-unsafe/

https://blog.csdn.net/sherld/article/details/42492259

相關文章
相關標籤/搜索