CAS的全稱是compare and swap,它是java同步類的基礎,java.util.concurrent中的同步類基本上都是使用CAS來實現其原子性的。java
CAS的原理其實很簡單,爲了保證在多線程環境下咱們的更新是符合預期的,或者說一個線程在更新某個對象的時候,沒有其餘的線程對該對象進行修改。在線程更新某個對象(或值)以前,先保存更新前的值,而後在實際更新的時候傳入以前保存的值,進行比較,若是一致的話就進行更新,不然失敗。git
注意,CAS在java中是用native方法來實現的,利用了系統自己提供的原子性操做。程序員
那麼CAS在使用中會有什麼問題呢?通常來講CAS若是設計的不夠完美的話,可能會產生ABA問題,而ABA問題又能夠分爲兩類,咱們先看來看一類問題。github
更多精彩內容且看:算法
更多內容請訪問www.flydean.com編程
咱們考慮下面一種ABA的狀況:多線程
上面的例子中CAS成功了,可是實際上這個CAS並非原子操做,若是咱們想要依賴CAS來實現原子操做的話可能就會出現隱藏的bug。編程語言
第一類問題的關鍵就在2和3兩步。這兩步咱們能夠看到線程b直接替換了內存地址X中的內容。post
在擁有自動GC環境的編程語言,好比說java中,2,3的狀況是不可能出現的,由於在java中,只要兩個對象的地址一致,就表示這兩個對象是相等的。區塊鏈
2,3兩步可能出現的狀況就在像C++這種,不存在自動GC環境的編程語言中。由於能夠本身控制對象的生命週期,若是咱們從一個list中刪除掉了一個對象,而後又從新分配了一個對象,並將其add back到list中去,那麼根據 MRU memory allocation算法,這個新的對象頗有可能和以前刪除對象的內存地址是同樣的。這樣就會致使ABA的問題。
若是咱們在擁有自動GC的編程語言中,那麼是否仍然存在CAS問題呢?
考慮下面的狀況,有一個鏈表裏面的數據是A->B->C,咱們但願執行一個CAS操做,將A替換成D,生成鏈表D->B->C。考慮下面的步驟:
最後咱們的到的鏈表是D->C,而不是D->B->C。
問題出在哪呢?CAS比較的節點A和最新的頭部節點是否是同一個節點,它並無關心節點A在步驟1和3之間是否內容發生變化。
咱們舉個例子:
public void useABAReference(){
CustUser a= new CustUser();
CustUser b= new CustUser();
CustUser c= new CustUser();
AtomicReference<CustUser> atomicReference= new AtomicReference<>(a);
log.info("{}",atomicReference.compareAndSet(a,b));
log.info("{}",atomicReference.compareAndSet(b,a));
a.setName("change for new name");
log.info("{}",atomicReference.compareAndSet(a,c));
}
複製代碼
上面的例子中,咱們使用了AtomicReference的CAS方法來判斷對象是否發生變化。在CAS b和a以後,咱們將a的name進行了修改,咱們看下最後的輸出結果:
[main] INFO com.flydean.aba.ABAUsage - true
[main] INFO com.flydean.aba.ABAUsage - true
[main] INFO com.flydean.aba.ABAUsage - true
複製代碼
三個CAS的結果都是true。說明CAS確實比較的二者是否爲統一對象,對其中內容的變化並不關心。
第二類問題可能會致使某些集合類的操做並非原子性的,由於你並不能保證在CAS的過程當中,有沒有其餘的節點發送變化。
第一類問題在存在自動GC的編程語言中是不存在的,咱們主要看下怎麼在C++之類的語言中解決這個問題。
根據官方的說法,第一類問題大概有四種解法:
第二類問題其實算是總體集合對象的CAS問題了。一個簡單的解決辦法就是每次作CAS更新的時候再添加一個版本號。若是版本號不是預期的版本,就說明有其餘的線程更新了集合中的某些節點,此次CAS是失敗的。
咱們舉個AtomicStampedReference的例子:
public void useABAStampReference(){
Object a= new Object();
Object b= new Object();
Object c= new Object();
AtomicStampedReference<Object> atomicStampedReference= new AtomicStampedReference(a,0);
log.info("{}",atomicStampedReference.compareAndSet(a,b,0,1));
log.info("{}",atomicStampedReference.compareAndSet(b,a,1,2));
log.info("{}",atomicStampedReference.compareAndSet(a,c,0,1));
}
複製代碼
AtomicStampedReference的compareAndSet方法,多出了兩個參數,分別是expectedStamp和newStamp,兩個參數都是int型的,須要咱們手動傳入。
ABA問題實際上是由兩類問題組成的,須要咱們分開來對待和解決。
本文的例子github.com/ddean2009/ learn-java-base-9-to-20
本文做者:flydean程序那些事
本文連接:www.flydean.com/aba-cas-sta…
本文來源:flydean的博客
歡迎關注個人公衆號:程序那些事,更多精彩等着您!