天天看點

Java中CAS原理分析(volatile和synchronized淺析)

CAS是什麼?

CAS英文解釋是比較和交換,是cpu底層的源語,是解決共享變量原子性實作方案,它定義了三個變量,記憶體位址值對應V,期待值E和要修改的值U,如下圖所示,這些變量都是在高速緩存中的,如果兩個線程A,B分别通過cas方式同時修改共享變量,假設當A線程先擷取時間片,如果發現V的值和E相等就将主記憶體值更新為U,如果不相等說明線程B線上程A更新之前已經成功更新過,線程A會失敗重試,此時根據緩存一緻性協定,線程A的本地副本會失效,需要從主記憶體再同步最新的變量到本地記憶體副本,在Java中通過調用UnSafe的compareAndSet類似方式調用,底層是c,反編譯後作業系統指令是cmpxchg指令。

Java中CAS原理分析(volatile和synchronized淺析)

保證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連用的效率, 這是我在深入思考後得到的結論,分享給大家。