(java併發)CAS操做原理以及Atomic的原理

CAS操做能夠分爲如下三個步驟:java

   步驟 1.讀舊值(即從系統內存中讀取所要使用的變量的值,例如:讀取變量i的值)算法

   步驟2.求新值(即對從內存中讀取的值進行操做,可是操做後不修改內存中變量的值,例如:i=i+1,這一步只進行                   i+1,沒有賦值,不對內存中的i進行修改)多線程

   步驟3.兩個不可分割的原子操做併發

              第一步:比較內存中變量如今的值與 最開始讀的舊值是否相同(即從內存中從新讀取i的值,與一開始讀取的                              i進行比較)框架

              第二步:若是這兩個值相同的話,則將求得的新值寫入內存中(即:i=i+1,更改內存中的i的值)測試

                             若是這兩個值不相同的話,則重複步驟1開始this

             注:這兩個操做是不可分割的原子操做,必須兩個同時完成atom

 

 

 

CAS操做

 

CAS是單詞compare and set的縮寫,意思是指在set以前先比較該值有沒有變化,只有在沒變的狀況下才對其賦值。spa

咱們經常作這樣的操做.net

 

if(a==b) {  
    a++;  
}

試想一下若是在作a++以前a的值被改變了怎麼辦?a++還執行嗎?出現該問題的緣由是在多線程環境下,a的值處於一種不定的狀態。採用鎖能夠解決此類問題,但CAS也能夠解決,並且能夠不加鎖。

int expect = a;  
if(a.compareAndSet(expect,a+1)) {  
    doSomeThing1();  
} else {  
    doSomeThing2();  
}

 

這樣若是a的值被改變了a++就不會被執行。

按照上面的寫法,a!=expect以後,a++就不會被執行,若是咱們仍是想執行a++操做怎麼辦,不要緊,能夠採用while循環

 

while(true) {  
    int expect = a;  
    if (a.compareAndSet(expect, a + 1)) {  
        doSomeThing1();  
        return;  
    } else {  
        doSomeThing2();  
    }  
}  

採用上面的寫法,在沒有鎖的狀況下實現了a++操做,這其實是一種非阻塞算法

 

應用

Java.util.concurrent.atomic包中幾乎大部分類都採用了CAS操做,以AtomicInteger爲例,看看它幾個主要方法的實現:

 

public final int getAndSet(int newValue) {  
    for (;;) {  
        int current = get();  
        if (compareAndSet(current, newValue))  
            return current;  
    }  
}  

getAndSet方法JDK文檔中的解釋是:以原子方式設置爲給定值,並返回舊值。原子方式體如今何處,就體如今compareAndSet上,看看compareAndSet是如何實現的:

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

不出所料,它就是採用的Unsafe類的CAS操做完成的。

 

 

 

再來看看a++操做是如何實現的:

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

幾乎和最開始的實例如出一轍,也是採用CAS操做來實現自增操做的。

 

++a操做和a++操做相似,只不過返回結果不一樣罷了

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

此外,java.util.concurrent.ConcurrentLinkedQueue類全是採用的非阻塞算法,裏面沒有使用任何鎖,全是基於CAS操做實現的。CAS操做能夠說是JAVA併發框架的基礎,整個框架的設計都是基於CAS操做的。

 

缺點:

一、ABA問題

CAS操做容易致使ABA問題,也就是在作a++之間,a可能被多個線程修改過了,只不過回到了最初的值,這時CAS會認爲a的值沒有變。a在外面逛了一圈回來,你能保證它沒有作任何壞事,不能!!也許它討閒,把b的值減了一下,把c的值加了一下等等,更有甚者若是a是一個對象,這個對象有多是新建立出來的,a是一個引用呢狀況又如何,因此這裏面仍是存在着不少問題的,解決ABA問題的方法有不少,能夠考慮增長一個修改計數,只有修改計數不變的且a值不變的狀況下才作a++,也能夠考慮引入版本號,當版本號相同時才作a++操做等,這和事務原子性處理有點相似!

二、比較花費CPU資源,即便沒有任何爭用也會作一些無用功。

三、會增長程序測試的複雜度,稍不注意就會出現問題。

 

總結:

能夠用CAS在無鎖的狀況下實現原子操做,但要明確應用場合,很是簡單的操做且又不想引入鎖能夠考慮使用CAS操做,當想要非阻塞地完成某一操做也能夠考慮CAS。不推薦在複雜操做中引入CAS,會使程序可讀性變差,且難以測試,同時會出現ABA問題。

相關文章
相關標籤/搜索