線程安全之CAS機制詳解(分析詳細,通俗易懂)


背景介紹:假設現在有一個線程共享的變量c=0,讓兩個線程分別對c進行c++操作100次,那么我們最后得到的結果是200嗎?

 

1.在線程不安全的方式下:結果可能小於200,比如當前線程A取得c的值為3,然后線程A阻塞了,線程B取得的c的值也是3,然后線程B也阻塞了,現在線程A被喚醒執行了++操作使得c=4,結果寫回c值內存,線程A執行結束,線程B被喚醒執行了++操作使得3++=4,也寫回了c值內存,現在問題來了,兩個線程分別進行了一次++操作,最后c值卻為4而不是5,所以c值最后的結果肯定是小於200的,產生這種情況的原因就是線程不安全!,兩個線程在同一時間讀取了c值,然后又沒有各種先執行完++操作而被阻塞(就是沒有同步)

 

2.在線程安全的方式下:比如++操作加上synchronized同步鎖,結果一定是200,因為這樣使得讀取c值和++操作是一個原子性操作,不能被打斷,所以線程是安全的,保證了同步

 

現在問題來了,我們要保證線程安全只有加synchorized同步鎖這一種辦法嗎?synchorized同步鎖又有什么缺點呢?

當然不僅只有synchorized這一種方法,還有原子操作類,關於原子操作類我們等下再說,先說說synchorized的缺點:

syschorized缺點:

synchorized的缺點關鍵在於性能!我們知道synchorized關鍵字會讓沒有得到鎖資源的線程進入Blocked狀態,而在得到鎖的資源恢復為Runnable狀態,這個過程涉及到操作系統用戶模式和內核模式的切換,代價比較高!

 

現在我們來說說原子操作類,顧名思義,就是保證某個操作的原子性,那它是怎么實現的呢?這個我們就要垃圾原子操作類的底層:CAS機制了

 

CAS機制的英文縮寫是Compare and Swap,翻譯一下就是比較和交換

CAS機制中使用3個基本操作數:內存地址V,舊的預期值A,要修改的新值B,更新一個變量的時候,只有當變量的舊的預期值A和內存地址V中的值相同的時候,才會將內存地址V中的值更新為新值B

 

下面舉個栗子:

1)內存地址V中存放着值為10的變量

2)此時線程1要把變量值加1,對線程1來說,舊的預期值A=10,要修改的新值B=11

3)在線程1提交更新之前,另外一個線程2提前一步將內存地址V中的變量值率先更新成了11

4)線程1此時開始提交更新,首先進行A和內存地址V中的值比較,發現A不等於此時內存地址V中的值11,提交失敗

5)線程1嘗試重新獲取內存地址V的當前值,並重新計算想要修改的值,對線程1來說,此時舊的預期值A=11,要修改的新值B=12,這個重新嘗試的過程叫做自旋

6)這一次比較幸運,沒有其他線程更改內存地址V中的值,線程1進行compare,發現A和內存地址V中的值相同

7)線程1進行Swap,把內存地址V中的值替換為B,也就是12

 

這個過程涉及到以下幾個問題:

問題1:如何保證獲取的當前值是內存中的最新值?(如果每次獲得的當前值不是內存中的最新值,那么CAS機制將毫無意義)

用volatile關鍵字修飾變量,使得每次對變量的修改操作完成后一定會先寫回內存,保證了每次獲取到值都是內存中的最新值

 

問題2:如何保證Compare和Swap過程中的原子性(如果Compare和Swap過程不是原子性操作,那么CAS機制也毫無意義)?

Compare和Swap過程的原子性是通過unsafe類來實現的,unsafe類為我們提供了硬件級別的原子操作!

 

總結一下:從思想上來說,Synchorized屬於悲觀鎖,悲觀的認為程序中的並發多,所以嚴防死守,CAS機制屬於樂觀鎖,樂觀的認為程序中並發少,讓線程不斷的去嘗試更新

 

那么現在又有一個問題來了,CAS機制有什么缺點呢?

CAS機制的缺點:

 

1.CPU開銷過大:在並發量比較高的情況下,如果許多線程反復嘗試去更新一個變量,卻又一直更新失敗,循環往復,會消耗CPU很多資源

 

2.ABA問題:假設在內存中有一個值為A的變量儲存在內存地址V當中,此時有三個線程使用CAS機制更新這個變量的值,每個線程的執行時間都略有偏差,線程1和線程2已經獲取當前值,線程3還沒有獲取當前值。接下來線程1先一步執行成功,把當前值成功從A更新為B,同時線程2因為某種原因被阻塞,沒有做更新操作,線程3在線程1更新成功之后獲取了當前值B,再之后線程2仍然阻塞,線程3繼續執行,成功將當前值更新為A,最后,線程2終於恢復了運行狀態,由於線程2之前獲取了“當前值A”並且經過了Compare檢測,內存地址中的實際值也是A,所以線程2最后把變量A更新成了B,在這個過程中,線程2獲取的當前值是一個舊值,盡管和當前值一模一樣,但是內存地址中V中的變量已經經歷了A->B->A的改變

表面看沒有什么影響,但是如果實際中理由CAS機制從取款機上取錢,假如賬戶開始有100元,在取款機上取走50,取款機出現問題一共提交了兩次請求(線程1,線程2),第二次請求(線程2)在執行時因為某種原因被阻塞了,這時候有人往你的賬戶打了50元,線程2恢復了可執行狀態,這個時候就會出現問題,原本線程2應該執行失敗的,但是比較后仍然與舊值一致,這樣就造成了賬戶實際上扣款了兩次!

 

ABA問題解決的方案:在Compare階段不僅比較預期值和此時內存中的值,還比較兩個比較變量的版本號是否一致,只有當版本號一致才進行后續操作,這樣就完美的解決了ABA問題!

 

3.不能保證代碼塊的原子性:CAS機制保證的是一個變量的原子性操作,若要保證多個變量的原子性操作,可以封裝在一起,但是這樣得不償失,開銷太大,還不如直接采用synchorized同步鎖

 

 

 

 

 

 

 


免責聲明!

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



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