日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

对CAS机制的理解(二)

發(fā)布時(shí)間:2025/4/16 编程问答 34 豆豆
生活随笔 收集整理的這篇文章主要介紹了 对CAS机制的理解(二) 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

一、Java當(dāng)中CAS的底層實(shí)現(xiàn)
首先看看AtomicInteger的源碼,AtomicInteger中常用的自增方法 incrementAndGet:

public final int incrementAndGet() {for (;;) {int current = get();int next = current + 1;if (compareAndSet(current, next))return next;} } private volatile int value; public final int get() {return value; }

這段代碼是一個(gè)無限循環(huán),也就是CAS的自旋。循環(huán)體當(dāng)中做了三件事:
1.獲取當(dāng)前值。
2.當(dāng)前值+1,計(jì)算出目標(biāo)值。
3.進(jìn)行CAS操作,如果成功則跳出循環(huán),如果失敗則重復(fù)上述步驟。

這里需要注意的重點(diǎn)是 get 方法,這個(gè)方法的作用是獲取變量的當(dāng)前值。
如何保證獲得的當(dāng)前值是內(nèi)存中的最新值呢?用volatile關(guān)鍵字來保證。

下面來看compareAndSet方法是如何保證原子性操作的。
compareAndSet方法的實(shí)現(xiàn):

compareAndSet方法的實(shí)現(xiàn)很簡單,只有一行代碼。這里涉及到兩個(gè)重要的對(duì)象,一個(gè)是unsafe,一個(gè)是valueOffset。

什么是unsafe呢?Java語言不像C,C++那樣可以直接訪問底層操作系統(tǒng),但是JVM為我們提供了一個(gè)后門,這個(gè)后門就是unsafe。unsafe為我們提供了硬件級(jí)別的原子操作。

至于valueOffset對(duì)象,是通過unsafe.objectFieldOffset方法得到,所代表的是AtomicInteger對(duì)象value成員變量在內(nèi)存中的偏移量。我們可以簡單地把valueOffset理解為value變量的內(nèi)存地址。

上一篇說過,CAS機(jī)制當(dāng)中使用了3個(gè)基本操作數(shù):內(nèi)存地址V,舊的預(yù)期值A(chǔ),要修改的新值B。

而unsafe的compareAndSwapInt方法參數(shù)包括了這三個(gè)基本元素:valueOffset參數(shù)代表了V,expect參數(shù)代表了A,update參數(shù)代表了B。

正是unsafe的compareAndSwapInt方法保證了Compare和Swap操作之間的原子性操作。

二、CAS的ABA問題和解決方法
所謂ABA問題,就是一個(gè)變量的值從A改成了B,又從B改成了A。
1.假設(shè)內(nèi)存中有一個(gè)值為A的變量,存儲(chǔ)在地址V當(dāng)中。

2.此時(shí)有三個(gè)線程想使用CAS的方式更新這個(gè)變量值,每個(gè)線程的執(zhí)行時(shí)間有略微的偏差。線程1和線程2已經(jīng)獲得當(dāng)前值,線程3還未獲得當(dāng)前值。

3.接下來,線程1先一步執(zhí)行成功,把當(dāng)前值成功從A更新為B;同時(shí)線程2因?yàn)槟撤N原因被阻塞住,沒有做更新操作;線程3在線程1更新之后,獲得了當(dāng)前值B。

4.再之后,線程2仍然處于阻塞狀態(tài),線程3繼續(xù)執(zhí)行,成功把當(dāng)前值從B更新成了A。

5.最后,線程2終于恢復(fù)了運(yùn)行狀態(tài),由于阻塞之前已經(jīng)獲得了“當(dāng)前值”A,并且經(jīng)過compare檢測(cè),內(nèi)存地址V中的實(shí)際值也是A,所以成功把變量值A(chǔ)更新成了B。

這個(gè)過程中,線程2獲取到的變量值A(chǔ)是一個(gè)舊值,盡管和當(dāng)前的實(shí)際值相同,但內(nèi)存地址V中的變量已經(jīng)經(jīng)歷了A->B->A的改變。
這個(gè)例子表面上看似沒什么毛病,因?yàn)楸緛砭褪且袮更新成B。下面結(jié)合實(shí)際應(yīng)用場(chǎng)景,舉一個(gè)提款機(jī)的栗子:
1.假設(shè)有一個(gè)遵循CAS原理的提款機(jī),小灰有100元存款,要用這個(gè)提款機(jī)來提款50元。

2.由于提款機(jī)硬件出了點(diǎn)小問題,小灰的提款操作被同時(shí)提交兩次,開啟了兩個(gè)線程,兩個(gè)線程都是獲取當(dāng)前值100元,要更新成50元。
(理想情況下,應(yīng)該一個(gè)線程更新成功,另一個(gè)線程更新失敗,小灰的存款只被扣一次。)

3.線程1首先執(zhí)行成功,把余額從100改成50。線程2因?yàn)槟撤N原因阻塞了。這時(shí)候,小灰的媽媽剛好給小灰匯款50元。

4.線程2仍然是阻塞狀態(tài),線程3執(zhí)行成功,把余額從50改成100。

5.線程2恢復(fù)運(yùn)行,由于阻塞之前已經(jīng)獲得了“當(dāng)前值”100,并且經(jīng)過compare檢測(cè),此時(shí)存款實(shí)際值也是100,所以成功把變量值100更新成了50。

原本線程2應(yīng)當(dāng)提交失敗,小灰的正確余額應(yīng)該保持為100元,結(jié)果由于ABA問題提交成功了。
ABA問題的解決方法:加個(gè)版本號(hào)。真正要做到嚴(yán)謹(jǐn)?shù)腃AS機(jī)制,我們?cè)贑ompare階段不僅要比較期望值A(chǔ)和地址V中的實(shí)際值,還要比較變量的版本號(hào)是否一致。
仍然以上一篇的栗子來說明,
1.假設(shè)地址V中存儲(chǔ)著變量值A(chǔ),當(dāng)前版本號(hào)是01。線程1獲得了當(dāng)前值A(chǔ)和版本號(hào)01,想要更新為B,但是被阻塞了。

2.這時(shí)候,內(nèi)存地址V中的變量發(fā)生了多次改變,版本號(hào)提升為03,但是變量值仍然是A。

3.隨后線程1恢復(fù)運(yùn)行,進(jìn)行Compare操作。經(jīng)過比較,線程1所獲得的值和地址V的實(shí)際值都是A,但是版本號(hào)不相等,所以這一次更新失敗。

在Java當(dāng)中,AtomicStampedReference類就實(shí)現(xiàn)了用版本號(hào)做比較的CAS機(jī)制。
回顧
1. Java語言CAS底層如何實(shí)現(xiàn)?
利用unsafe提供了原子性操作方法。

2. 什么是ABA問題?怎么解決?
當(dāng)一個(gè)值從A更新成B,又更新會(huì)A,普通CAS機(jī)制會(huì)誤判通過檢測(cè)。
利用版本號(hào)比較可以有效解決ABA問題。

參考:漫畫:什么是CAS機(jī)制?(進(jìn)階篇)

來自公眾號(hào):

轉(zhuǎn)載于:https://www.cnblogs.com/zeroingToOne/p/8955396.html

《新程序員》:云原生和全面數(shù)字化實(shí)踐50位技術(shù)專家共同創(chuàng)作,文字、視頻、音頻交互閱讀

總結(jié)

以上是生活随笔為你收集整理的对CAS机制的理解(二)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。