CAS是什麼?
CAS英文解釋是比較和交換,是cpu底層的源語,是解決共享變數原子性實現方案,它定義了三個變數,記憶體地址值對應V,期待值E和要修改的值U,如下圖所示,這些變數都是在快取記憶體中的,如果兩個執行緒A,B分別透過cas方式同時修改共享變數,假設當A執行緒先獲取時間片,如果發現V的值和E相等就將主記憶體值更新為U,如果不相等說明執行緒B線上程A更新之前已經成功更新過,執行緒A會失敗重試,此時根據快取一致性協議,執行緒A的本地副本會失效,需要從主記憶體再同步最新的變數到本地記憶體副本,在Java中透過呼叫UnSafe的compareAndSet類似方式呼叫,底層是c,反編譯後作業系統指令是cmpxchg指令。
保證i++原子性你一定會有一個疑問,被 volatile 修飾的變數i,i++為什麼會有執行緒安全問題呢,也就是原子性的問題,我們還是舉一個經典的i++案例一步步分析吧!我們知道在多執行緒情況下volatile保證了共享變數的可見性,順序行,但唯獨不能保證原子性,原因是i++是一個複合操作,大致可以分成3步,1.先從主記憶體拿到最新的i值,2.將i加1這個操作儲存到運算元棧,3.從棧中取出i加1的值寫回到主記憶體。OK,當執行緒AB同時執行i++操作時,比如執行緒A先獲取時間片,執行完第2步,這是執行緒A還未執行完,時間片分配給執行緒B,B順利執行完所有操作後並同步了主記憶體,假設我們i的初始值是1,那麼此時主記憶體值是2,因為執行緒B執行完畢,cpu時間片又回到執行緒A手上,做第3步操作,此時同步到主記憶體的值還是2,看,執行緒A,B各做了一次加1的操作,但最終結果可能是2,cas的作用就來了,他能保證i++操作的原子性,為什麼能保證原子性呢?cas可以把上面三個操作合併成一個操作,是原子的。
有什麼好處?大家都知道解決多執行緒安全需要用到鎖的,可以用 synchronized 來解決,但是synchronized也有它的劣勢,最主要是它是阻塞的,阻塞會有什麼問題?效能啊,這是計算機人不能忍的,頻繁核心外核切換,會嚴重浪費系統資源,所以就提了cas這個樂觀鎖概念,它是非阻塞的,作業系統不用在核心態與使用者態來回切換,相當於用while迴圈方式獲取鎖,在效能上有一定提升。即使這樣,也會有一定問題,下面我們來看看。
有什麼問題?1.ABA問題。
這個案例比較簡單,執行緒A把共享變數i,從1變成2,再變成1,執行緒B想把i變成2,本來應該是不會成功,因為即時變數i現在是1,但是它的狀態變化了,他的解決方案是版本號。相當於修改成功一次版本號增加1,就可以解決了,曾經被面試官問到一個問題,cas是執行緒安全的嗎?答案不是執行緒安全的。
2.自旋時間過長。
如果一個執行緒拿到鎖後,一直不釋放,其他執行緒就只能一直迴圈等待。
3.只能保證一個共享變數的原子性。
像Automic包下面的基本上都只能保證一個變數的原子性。
JUC包下面使用!可能有些童鞋看JDK原始碼會比較糾結一個點, 發現volatile關鍵字一般都會和cas連用,如果不要volatile會怎麼樣呢 ?cas本身只作用於方法,cas對共享變數沒有約束,如果不對共享變數做volatile修飾,是不可見的,不能夠保證共享變數的實效性,需要等待共享變數主動同步到主記憶體,這是需要花時間的,效率更低下,所有在JUC併發包裡一直可以看到這樣的 volatile關鍵字一般都會和cas 組合。
總結這篇文章,我們先引出了cas概念,並且說明了它的優缺點,做了案例介紹,簡單的和synchronized關鍵字做了比較,最後,深入的說明了 volatile關鍵字 和 cas連用的效率, 這是我在深入思考後得到的結論。