[轉] java CAS

來源:互聯網
上載者:User

標籤:版本號碼   檢查   因此   避免   變化   通用   線程之間的通訊   off   取數   

參考連結 :

http://blog.csdn.net/liu88010988/article/details/50799978

 

 

java.util.concurrent包完全建立在CAS之上的,沒有CAS就不會有此包。可見CAS的重要性。

 

CAS

CAS:Compare and Swap, 翻譯成比較並交換。 

java.util.concurrent包中藉助CAS實現了區別於synchronouse同步鎖的一種樂觀鎖。

 

CAS應用

CAS有3個運算元,記憶體值V,舊的預期值A,要修改的新值B。若且唯若預期值A和記憶體值V相同時,將記憶體值V修改為B,否則什麼都不做。

 

現代的CPU提供了特殊的指令,可以自動更新共用資料,而且能夠檢測到其他線程的幹擾,而 compareAndSet() 就用這些代替了鎖定。

拿出AtomicInteger來研究在沒有鎖的情況下是如何做到資料正確性的。

private volatile int value;

首先毫無疑問,在沒有鎖的機制下可能需要藉助volatile原語,保證線程間的資料是可見的(共用的)。

這樣才擷取變數的值的時候才能直接讀取。

public final int get() {
        return value;
    }

然後來看看++i是怎麼做到的。

public final int incrementAndGet() {
    for (;;) {
        int current = get();
        int next = current + 1;
        if (compareAndSet(current, next))
            return next;
    }
}

在這裡採用了CAS操作,每次從記憶體中讀取資料然後將此資料和+1後的結果進行CAS操作,如果成功就返回結果,否則重試直到成功為止。

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

public final boolean compareAndSet(int expect, int update) {   
    return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
    }

整體的過程就是這樣子的,利用CPU的CAS指令,同時藉助JNI來完成Java的非阻塞演算法。其它原子操作都是利用類似的特性完成的。

 

其中

unsafe.compareAndSwapInt(this, valueOffset, expect, update);

類似:

if (this == expect) {

  this = update

 return true;

} else {

return false;

}

 

 

CAS缺點

 CAS雖然很高效的解決原子操作,但是CAS仍然存在三大問題。ABA問題,迴圈時間長開銷大和只能保證一個共用變數的原子操作

1.  ABA問題。因為CAS需要在操作值的時候檢查下值有沒有發生變化,如果沒有發生變化則更新,但是如果一個值原來是A,變成了B,又變成了A,那麼使用CAS進行檢查時會發現它的值沒有發生變化,但是實際上卻變化了。ABA問題的解決思路就是使用版本號碼。在變數前面追加上版本號碼,每次變數更新的時候把版本號碼加一,那麼A-B-A 就會變成1A-2B-3A。

從Java1.5開始JDK的atomic包裡提供了一個類AtomicStampedReference來解決ABA問題。這個類的compareAndSet方法作用是首先檢查當前引用是否等於預期引用,並且當前標誌是否等於預期標誌,如果全部相等,則以原子方式將該引用和該標誌的值設定為給定的更新值。

關於ABA問題參考文檔: http://blog.hesey.net/2011/09/resolve-aba-by-atomicstampedreference.html

2. 迴圈時間長開銷大。自旋CAS如果長時間不成功,會給CPU帶來非常大的執行開銷。如果JVM能支援處理器提供的pause指令那麼效率會有一定的提升,pause指令有兩個作用,第一它可以延遲流水線執行指令(de-pipeline),使CPU不會消耗過多的執行資源,延遲的時間取決於具體實現的版本,在一些處理器上延遲時間是零。第二它可以避免在退出迴圈的時候因記憶體順序衝突(memory order violation)而引起CPU流水線被清空(CPU pipeline flush),從而提高CPU的執行效率。

 

3. 只能保證一個共用變數的原子操作。當對一個共用變數執行操作時,我們可以使用迴圈CAS的方式來保證原子操作,但是對多個共用變數操作時,迴圈CAS就無法保證操作的原子性,這個時候就可以用鎖,或者有一個取巧的辦法,就是把多個共用變數合并成一個共用變數來操作。比如有兩個共用變數i=2,j=a,合并一下ij=2a,然後用CAS來操作ij。從Java1.5開始JDK提供了AtomicReference類來保證引用對象之間的原子性,你可以把多個變數放在一個對象裡來進行CAS操作。

 

concurrent包的實現

由於java的CAS同時具有 volatile 讀和volatile寫的記憶體語義,因此Java線程之間的通訊現在有了下面四種方式:

  1. A線程寫volatile變數,隨後B線程讀這個volatile變數。
  2. A線程寫volatile變數,隨後B線程用CAS更新這個volatile變數。
  3. A線程用CAS更新一個volatile變數,隨後B線程用CAS更新這個volatile變數。
  4. A線程用CAS更新一個volatile變數,隨後B線程讀這個volatile變數。

Java的CAS會使用現代處理器上提供的高效機器層級原子指令,這些原子指令以原子方式對記憶體執行讀-改-寫操作,這是在多處理器中實現同步的關鍵(從本質上來說,能夠支援原子性讀-改-寫指令的電腦器,是順序計算圖靈機的非同步等價機器,因此任何現代的多處理器都會去支援某種能對記憶體執行原子性讀-改-寫操作的原子指令)。同時,volatile變數的讀/寫和CAS可以實現線程之間的通訊。把這些特性整合在一起,就形成了整個concurrent包得以實現的基石。如果我們仔細分析concurrent包的原始碼實現,會發現一個通用化的實現模式:

  1. 首先,聲明共用變數為volatile;
  2. 然後,使用CAS的原子條件更新來實現線程之間的同步;
  3. 同時,配合以volatile的讀/寫和CAS所具有的volatile讀和寫的記憶體語義來實現線程之間的通訊。

AQS AbstractQueuedSynchronizer ,非阻塞資料結構和原子變數類(java.util.concurrent.atomic包中的類),這些concurrent包中的基礎類都是使用這種模式來實現的,而concurrent包中的高層類又是依賴於這些基礎類來實現的。從整體來看,concurrent包的實現如下:

[轉] java CAS

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.