這篇文章給大家分享的是有關(guān)java中CAS是什么的內(nèi)容。小編覺得挺實(shí)用的,因此分享給大家做個(gè)參考,一起跟隨小編過來看看吧。
10年的東湖網(wǎng)站建設(shè)經(jīng)驗(yàn),針對設(shè)計(jì)、前端、開發(fā)、售后、文案、推廣等六對一服務(wù),響應(yīng)快,48小時(shí)及時(shí)工作處理。網(wǎng)絡(luò)營銷推廣的優(yōu)勢是能夠根據(jù)用戶設(shè)備顯示端的尺寸不同,自動調(diào)整東湖建站的顯示方式,使網(wǎng)站能夠適用不同顯示終端,在瀏覽器中調(diào)整網(wǎng)站的寬度,無論在任何一種瀏覽器上瀏覽網(wǎng)站,都能展現(xiàn)優(yōu)雅布局與設(shè)計(jì),從而大程度地提升瀏覽體驗(yàn)。成都創(chuàng)新互聯(lián)公司從事“東湖網(wǎng)站設(shè)計(jì)”,“東湖網(wǎng)站推廣”以來,每個(gè)客戶項(xiàng)目都認(rèn)真落實(shí)執(zhí)行。
Java并發(fā)編程系列番外篇C A S(Compare and swap)
,文章風(fēng)格依然是圖文并茂,通俗易懂,讓讀者們也能與面試官瘋狂對線。
C A S
作為并發(fā)編程必不可少的基礎(chǔ)知識,面試時(shí)C A S
也是個(gè)高頻考點(diǎn),所以說C A S
是必知必會,本文將帶讀者們深入理解C A S
。
C A S(compareAndSwap)
也叫比較交換,是一種無鎖原子算法,映射到操作系統(tǒng)就是一條cmpxchg
硬件匯編指令(保證原子性),其作用是讓C P U
將內(nèi)存值更新為新值,但是有個(gè)條件,內(nèi)存值必須與期望值相同,并且C A S
操作無需用戶態(tài)與內(nèi)核態(tài)切換,直接在用戶態(tài)對內(nèi)存進(jìn)行讀寫操作(意味著不會阻塞/線程上下文切換)。
它包含3
個(gè)參數(shù)C A S(V,E,N)
,V
表示待更新的內(nèi)存值,E
表示預(yù)期值,N
表示新值,當(dāng) V
值等于E
值時(shí),才會將V
值更新成N
值,如果V
值和E
值不等,不做更新,這就是一次C A S
的操作。
簡單說,C A S
需要你額外給出一個(gè)期望值,也就是你認(rèn)為這個(gè)變量現(xiàn)在應(yīng)該是什么樣子的,如果變量不是你想象的那樣,說明它已經(jīng)被別人修改過了,你只需要重新讀取,設(shè)置新期望值,再次嘗試修改就好了。
原子性是指一個(gè)或者多個(gè)操作在C P U
執(zhí)行的過程中不被中斷的特性,要么執(zhí)行,要不執(zhí)行,不能執(zhí)行到一半(不可被中斷的一個(gè)或一系列操作)。
為了保證C A S
的原子性,C P U
提供了下面兩種方式
總線鎖定
緩存鎖定
總線(B U S
)是計(jì)算機(jī)組件間的傳輸數(shù)據(jù)方式,也就是說C P U
與其他組件連接傳輸數(shù)據(jù),就是靠總線完成的,比如C P U
對內(nèi)存的讀寫。
總線鎖定是指C P U
使用了總線鎖,所謂總線鎖就是使用C P U
提供的LOCK#
信號,當(dāng)C P U
在總線上輸出LOCK#
信號時(shí),其他C P U
的總線請求將被阻塞。
總線鎖定方式雖然保證了原子性,但是在鎖定期間,會導(dǎo)致大量阻塞,增加系統(tǒng)的性能開銷,所以現(xiàn)代C P U
為了提升性能,通過鎖定范圍縮小的思想設(shè)計(jì)出了緩存行鎖定(緩存行是C P U
高速緩存存儲的最小單位)。
所謂緩存鎖定是指C P U
對緩存行進(jìn)行鎖定,當(dāng)緩存行中的共享變量回寫到內(nèi)存時(shí),其他C P U
會通過總線嗅探機(jī)制感知該共享變量是否發(fā)生變化,如果發(fā)生變化,讓自己對應(yīng)的共享變量緩存行失效,重新從內(nèi)存讀取最新的數(shù)據(jù),緩存鎖定是基于緩存一致性機(jī)制來實(shí)現(xiàn)的,因?yàn)榫彺嬉恢滦詸C(jī)制會阻止兩個(gè)以上C P U
同時(shí)修改同一個(gè)共享變量(現(xiàn)代C P U
基本都支持和使用緩存鎖定機(jī)制)。
C A S
和鎖都解決了原子性問題,和鎖相比沒有阻塞、線程上下文你切換、死鎖,所以C A S
要比鎖擁有更優(yōu)越的性能,但是C A S
同樣存在缺點(diǎn)。
C A S
的問題如下
只能保證一個(gè)共享變量的原子操作
自旋時(shí)間太長(建立在自旋鎖的基礎(chǔ)上)
ABA
問題
C A S
只能針對一個(gè)共享變量使用,如果多個(gè)共享變量就只能使用鎖了,當(dāng)然如果你有辦法把多個(gè)變量整成一個(gè)變量,利用C A S
也不錯,例如讀寫鎖中state
的高低位。
當(dāng)一個(gè)線程獲取鎖時(shí)失敗,不進(jìn)行阻塞掛起,而是間隔一段時(shí)間再次嘗試獲取,直到成功為止,這種循環(huán)獲取的機(jī)制被稱為自旋鎖(spinlock
)。
自旋鎖好處是,持有鎖的線程在短時(shí)間內(nèi)釋放鎖,那些等待競爭鎖的線程就不需進(jìn)入阻塞狀態(tài)(無需線程上下文切換/無需用戶態(tài)與內(nèi)核態(tài)切換),它們只需要等一等(自旋),等到持有鎖的線程釋放鎖之后即可獲取,這樣就避免了用戶態(tài)和內(nèi)核態(tài)的切換消耗。
自旋鎖壞處顯而易見,線程在長時(shí)間內(nèi)持有鎖,等待競爭鎖的線程一直自旋,即CPU一直空轉(zhuǎn),資源浪費(fèi)在毫無意義的地方,所以一般會限制自旋次數(shù)。
最后來說自旋鎖的實(shí)現(xiàn),實(shí)現(xiàn)自旋鎖可以基于C A S
實(shí)現(xiàn),先定義lockValue
對象默認(rèn)值1
,1
代表鎖資源空閑,0
代表鎖資源被占用,代碼如下
public class SpinLock { //lockValue 默認(rèn)值1 private AtomicInteger lockValue = new AtomicInteger(1); //自旋獲取鎖 public void lock(){ // 循環(huán)檢測嘗試獲取鎖 while (!tryLock()){ // 空轉(zhuǎn) } } //獲取鎖 public boolean tryLock(){ // 期望值1,更新值0,更新成功返回true,更新失敗返回false return lockValue.compareAndSet(1,0); } //釋放鎖 public void unLock(){ if(!lockValue.compareAndSet(1,0)){ throw new RuntimeException("釋放鎖失敗"); } } }
上面定義了AtomicInteger
類型的lockValue
變量,AtomicInteger
是Java
基于C A S
實(shí)現(xiàn)的Integer
原子操作類,還定義了3個(gè)函數(shù)lock、tryLock、unLock
tryLock函數(shù)-獲取鎖
期望值1,更新值0
C A S
更新
如果期望值與lockValue
值相等,則lockValue
值更新為0
,返回true
,否則執(zhí)行下面邏輯
如果期望值與lockValue
值不相等,不做任何更新,返回false
unLock函數(shù)-釋放鎖
期望值0
,更新值1
C A S
更新
如果期望值與lockValue
值相等,則lockValue
值更新為1
,返回true
,否則執(zhí)行下面邏輯
如果期望值與lockValue
值不相等,不做任何更新,返回false
lock函數(shù)-自旋獲取鎖
執(zhí)行tryLock
函數(shù),返回true
停止,否則一直循環(huán)
從上圖可以看出,只有tryLock
成功的線程(把lockValue
更新為0
),才會執(zhí)行代碼塊,其他線程個(gè)tryLock
自旋等待lockValue
被更新成1
,tryLock
成功的線程執(zhí)行unLock
(把lockValue
更新為1
),自旋的線程才會tryLock
成功。
C A S
需要檢查待更新的內(nèi)存值有沒有被修改,如果沒有則更新,但是存在這樣一種情況,如果一個(gè)值原來是A
,變成了B
,然后又變成了A
,在C A S
檢查的時(shí)候會發(fā)現(xiàn)沒有被修改。
假設(shè)有兩個(gè)線程,線程1
讀取到內(nèi)存值A
,線程1
時(shí)間片用完,切換到線程2
,線程2
也讀取到了內(nèi)存值A
,并把它修改為B
值,然后再把B
值還原到A
值,簡單說,修改次序是A->B->A
,接著線程1
恢復(fù)運(yùn)行,它發(fā)現(xiàn)內(nèi)存值還是A
,然后執(zhí)行C A S
操作,這就是著名的ABA
問題,但是好像又看不出什么問題。
只是簡單的數(shù)據(jù)結(jié)構(gòu),確實(shí)不會有什么問題,如果是復(fù)雜的數(shù)據(jù)結(jié)構(gòu)可能就會有問題了(使用AtomicReference
可以把C A S
使用在對象上),以鏈表數(shù)據(jù)結(jié)構(gòu)為例,兩個(gè)線程通過C A S
去刪除頭節(jié)點(diǎn),假設(shè)現(xiàn)在鏈表有A->B
節(jié)點(diǎn)
線程1
刪除A
節(jié)點(diǎn),B
節(jié)點(diǎn)成為頭節(jié)點(diǎn),正要執(zhí)行C A S(A,A,B)
時(shí),時(shí)間片用完,切換到線程2
線程2
刪除A、B
節(jié)點(diǎn)
線程2
加入C、A
節(jié)點(diǎn),鏈表節(jié)點(diǎn)變成A->C
線程1
重新獲取時(shí)間片,執(zhí)行C A S(A,A,B)
丟失C
節(jié)點(diǎn)
要解決A B A
問題也非常簡單,只要追加版本號即可,每次改變時(shí)加1
,即A —> B —> A
,變成1A —> 2B —> 3A
,在Java
中提供了AtomicStampedRdference
可以實(shí)現(xiàn)這個(gè)方案(面試只要問了C A S
,就一定會問ABA
,這塊一定要搞明白)。
感謝各位的閱讀!關(guān)于“java中CAS是什么”這篇文章就分享到這里了,希望以上內(nèi)容可以對大家有一定的幫助,讓大家可以學(xué)到更多知識,如果覺得文章不錯,可以把它分享出去讓更多的人看到吧!