CAS操做能夠分爲如下三個步驟:java
步驟 1.讀舊值(即從系統內存中讀取所要使用的變量的值,例如:讀取變量i的值)算法
步驟2.求新值(即對從內存中讀取的值進行操做,可是操做後不修改內存中變量的值,例如:i=i+1,這一步只進行 i+1,沒有賦值,不對內存中的i進行修改)多線程
步驟3.兩個不可分割的原子操做併發
第一步:比較內存中變量如今的值與 最開始讀的舊值是否相同(即從內存中從新讀取i的值,與一開始讀取的 i進行比較)框架
第二步:若是這兩個值相同的話,則將求得的新值寫入內存中(即:i=i+1,更改內存中的i的值)測試
若是這兩個值不相同的話,則重複步驟1開始this
注:這兩個操做是不可分割的原子操做,必須兩個同時完成atom
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問題。