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指令。

image.png

 

保證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連用的效率,這是我在深入思考后得到的結論,分享給大家,文章有一定閱讀門檻,如果有想搞清楚童鞋,可以1v1私聊討論交流。希望大家喜歡。點贊哦!

我是叫練,邊叫邊練,歡迎點贊和評論。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM