对CAS机制的理解(二)
一、Java當中CAS的底層實現(xiàn)
首先看看AtomicInteger的源碼,AtomicInteger中常用的自增方法 incrementAndGet:
這段代碼是一個無限循環(huán),也就是CAS的自旋。循環(huán)體當中做了三件事:
1.獲取當前值。
2.當前值+1,計算出目標值。
3.進行CAS操作,如果成功則跳出循環(huán),如果失敗則重復上述步驟。
這里需要注意的重點是 get 方法,這個方法的作用是獲取變量的當前值。
如何保證獲得的當前值是內(nèi)存中的最新值呢?用volatile關鍵字來保證。
下面來看compareAndSet方法是如何保證原子性操作的。
compareAndSet方法的實現(xiàn):
compareAndSet方法的實現(xiàn)很簡單,只有一行代碼。這里涉及到兩個重要的對象,一個是unsafe,一個是valueOffset。
什么是unsafe呢?Java語言不像C,C++那樣可以直接訪問底層操作系統(tǒng),但是JVM為我們提供了一個后門,這個后門就是unsafe。unsafe為我們提供了硬件級別的原子操作。
至于valueOffset對象,是通過unsafe.objectFieldOffset方法得到,所代表的是AtomicInteger對象value成員變量在內(nèi)存中的偏移量。我們可以簡單地把valueOffset理解為value變量的內(nèi)存地址。
上一篇說過,CAS機制當中使用了3個基本操作數(shù):內(nèi)存地址V,舊的預期值A,要修改的新值B。
而unsafe的compareAndSwapInt方法參數(shù)包括了這三個基本元素:valueOffset參數(shù)代表了V,expect參數(shù)代表了A,update參數(shù)代表了B。
正是unsafe的compareAndSwapInt方法保證了Compare和Swap操作之間的原子性操作。
二、CAS的ABA問題和解決方法
所謂ABA問題,就是一個變量的值從A改成了B,又從B改成了A。
1.假設內(nèi)存中有一個值為A的變量,存儲在地址V當中。
2.此時有三個線程想使用CAS的方式更新這個變量值,每個線程的執(zhí)行時間有略微的偏差。線程1和線程2已經(jīng)獲得當前值,線程3還未獲得當前值。
3.接下來,線程1先一步執(zhí)行成功,把當前值成功從A更新為B;同時線程2因為某種原因被阻塞住,沒有做更新操作;線程3在線程1更新之后,獲得了當前值B。
4.再之后,線程2仍然處于阻塞狀態(tài),線程3繼續(xù)執(zhí)行,成功把當前值從B更新成了A。
5.最后,線程2終于恢復了運行狀態(tài),由于阻塞之前已經(jīng)獲得了“當前值”A,并且經(jīng)過compare檢測,內(nèi)存地址V中的實際值也是A,所以成功把變量值A更新成了B。
這個過程中,線程2獲取到的變量值A是一個舊值,盡管和當前的實際值相同,但內(nèi)存地址V中的變量已經(jīng)經(jīng)歷了A->B->A的改變。
這個例子表面上看似沒什么毛病,因為本來就是要把A更新成B。下面結合實際應用場景,舉一個提款機的栗子:
1.假設有一個遵循CAS原理的提款機,小灰有100元存款,要用這個提款機來提款50元。
2.由于提款機硬件出了點小問題,小灰的提款操作被同時提交兩次,開啟了兩個線程,兩個線程都是獲取當前值100元,要更新成50元。
(理想情況下,應該一個線程更新成功,另一個線程更新失敗,小灰的存款只被扣一次。)
3.線程1首先執(zhí)行成功,把余額從100改成50。線程2因為某種原因阻塞了。這時候,小灰的媽媽剛好給小灰匯款50元。
4.線程2仍然是阻塞狀態(tài),線程3執(zhí)行成功,把余額從50改成100。
5.線程2恢復運行,由于阻塞之前已經(jīng)獲得了“當前值”100,并且經(jīng)過compare檢測,此時存款實際值也是100,所以成功把變量值100更新成了50。
原本線程2應當提交失敗,小灰的正確余額應該保持為100元,結果由于ABA問題提交成功了。
ABA問題的解決方法:加個版本號。真正要做到嚴謹?shù)腃AS機制,我們在Compare階段不僅要比較期望值A和地址V中的實際值,還要比較變量的版本號是否一致。
仍然以上一篇的栗子來說明,
1.假設地址V中存儲著變量值A,當前版本號是01。線程1獲得了當前值A和版本號01,想要更新為B,但是被阻塞了。
2.這時候,內(nèi)存地址V中的變量發(fā)生了多次改變,版本號提升為03,但是變量值仍然是A。
3.隨后線程1恢復運行,進行Compare操作。經(jīng)過比較,線程1所獲得的值和地址V的實際值都是A,但是版本號不相等,所以這一次更新失敗。
在Java當中,AtomicStampedReference類就實現(xiàn)了用版本號做比較的CAS機制。
回顧
1. Java語言CAS底層如何實現(xiàn)?
利用unsafe提供了原子性操作方法。
2. 什么是ABA問題?怎么解決?
當一個值從A更新成B,又更新會A,普通CAS機制會誤判通過檢測。
利用版本號比較可以有效解決ABA問題。
參考:漫畫:什么是CAS機制?(進階篇)
來自公眾號:
轉(zhuǎn)載于:https://www.cnblogs.com/zeroingToOne/p/8955396.html
《新程序員》:云原生和全面數(shù)字化實踐50位技術專家共同創(chuàng)作,文字、視頻、音頻交互閱讀總結
以上是生活随笔為你收集整理的对CAS机制的理解(二)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Java 读写Properties配置文
- 下一篇: 20172311『Java程序设计』课程