溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

java中CAS是什么

發(fā)布時(shí)間:2021-07-29 09:02:19 來(lái)源:億速云 閱讀:610 作者:小新 欄目:編程語(yǔ)言

這篇文章給大家分享的是有關(guān)java中CAS是什么的內(nèi)容。小編覺(jué)得挺實(shí)用的,因此分享給大家做個(gè)參考,一起跟隨小編過(guò)來(lái)看看吧。

前言

Java并發(fā)編程系列番外篇C A S(Compare and swap),文章風(fēng)格依然是圖文并茂,通俗易懂,讓讀者們也能與面試官瘋狂對(duì)線。

C A S作為并發(fā)編程必不可少的基礎(chǔ)知識(shí),面試時(shí)C A S也是個(gè)高頻考點(diǎn),所以說(shuō)C A S是必知必會(huì),本文將帶讀者們深入理解C A S。

大綱

java中CAS是什么

C A S基本概念

C A S(compareAndSwap)也叫比較交換,是一種無(wú)鎖原子算法,映射到操作系統(tǒng)就是一條cmpxchg硬件匯編指令(保證原子性),其作用是讓C P U將內(nèi)存值更新為新值,但是有個(gè)條件,內(nèi)存值必須與期望值相同,并且C A S操作無(wú)需用戶態(tài)與內(nèi)核態(tài)切換,直接在用戶態(tài)對(duì)內(nèi)存進(jìn)行讀寫操作(意味著不會(huì)阻塞/線程上下文切換)。

它包含3個(gè)參數(shù)C A S(V,E,N),V表示待更新的內(nèi)存值,E表示預(yù)期值,N表示新值,當(dāng) V值等于E值時(shí),才會(huì)將V值更新成N值,如果V值和E值不等,不做更新,這就是一次C A S的操作。

java中CAS是什么

簡(jiǎn)單說(shuō),C A S需要你額外給出一個(gè)期望值,也就是你認(rèn)為這個(gè)變量現(xiàn)在應(yīng)該是什么樣子的,如果變量不是你想象的那樣,說(shuō)明它已經(jīng)被別人修改過(guò)了,你只需要重新讀取,設(shè)置新期望值,再次嘗試修改就好了。

C A S如何保證原子性

原子性是指一個(gè)或者多個(gè)操作在C P U執(zhí)行的過(guò)程中不被中斷的特性,要么執(zhí)行,要不執(zhí)行,不能執(zhí)行到一半(不可被中斷的一個(gè)或一系列操作)。

為了保證C A S的原子性,C P U提供了下面兩種方式

  • 總線鎖定

  • 緩存鎖定

總線鎖定

總線(B U S)是計(jì)算機(jī)組件間的傳輸數(shù)據(jù)方式,也就是說(shuō)C P U與其他組件連接傳輸數(shù)據(jù),就是靠總線完成的,比如C P U對(duì)內(nèi)存的讀寫。

java中CAS是什么

總線鎖定是指C P U使用了總線鎖,所謂總線鎖就是使用C P U提供的LOCK#信號(hào),當(dāng)C P U在總線上輸出LOCK#信號(hào)時(shí),其他C P U的總線請(qǐng)求將被阻塞。

java中CAS是什么

緩存鎖定

總線鎖定方式雖然保證了原子性,但是在鎖定期間,會(huì)導(dǎo)致大量阻塞,增加系統(tǒng)的性能開(kāi)銷,所以現(xiàn)代C P U為了提升性能,通過(guò)鎖定范圍縮小的思想設(shè)計(jì)出了緩存行鎖定(緩存行是C P U高速緩存存儲(chǔ)的最小單位)。

所謂緩存鎖定是指C P U對(duì)緩存行進(jìn)行鎖定,當(dāng)緩存行中的共享變量回寫到內(nèi)存時(shí),其他C P U會(huì)通過(guò)總線嗅探機(jī)制感知該共享變量是否發(fā)生變化,如果發(fā)生變化,讓自己對(duì)應(yīng)的共享變量緩存行失效,重新從內(nèi)存讀取最新的數(shù)據(jù),緩存鎖定是基于緩存一致性機(jī)制來(lái)實(shí)現(xiàn)的,因?yàn)榫彺嬉恢滦詸C(jī)制會(huì)阻止兩個(gè)以上C P U同時(shí)修改同一個(gè)共享變量(現(xiàn)代C P U基本都支持和使用緩存鎖定機(jī)制)。

C A S的問(wèn)題

C A S和鎖都解決了原子性問(wèn)題,和鎖相比沒(méi)有阻塞、線程上下文你切換、死鎖,所以C A S要比鎖擁有更優(yōu)越的性能,但是C A S同樣存在缺點(diǎn)。

C A S的問(wèn)題如下

  • 只能保證一個(gè)共享變量的原子操作

  • 自旋時(shí)間太長(zhǎng)(建立在自旋鎖的基礎(chǔ)上)

  • ABA問(wèn)題

只能保證一個(gè)共享變量原子操作

C A S只能針對(duì)一個(gè)共享變量使用,如果多個(gè)共享變量就只能使用鎖了,當(dāng)然如果你有辦法把多個(gè)變量整成一個(gè)變量,利用C A S也不錯(cuò),例如讀寫鎖中state的高低位。

自旋時(shí)間太長(zhǎng)

當(dāng)一個(gè)線程獲取鎖時(shí)失敗,不進(jìn)行阻塞掛起,而是間隔一段時(shí)間再次嘗試獲取,直到成功為止,這種循環(huán)獲取的機(jī)制被稱為自旋鎖(spinlock)。

自旋鎖好處是,持有鎖的線程在短時(shí)間內(nèi)釋放鎖,那些等待競(jìng)爭(zhēng)鎖的線程就不需進(jìn)入阻塞狀態(tài)(無(wú)需線程上下文切換/無(wú)需用戶態(tài)與內(nèi)核態(tài)切換),它們只需要等一等(自旋),等到持有鎖的線程釋放鎖之后即可獲取,這樣就避免了用戶態(tài)和內(nèi)核態(tài)的切換消耗。

自旋鎖壞處顯而易見(jiàn),線程在長(zhǎng)時(shí)間內(nèi)持有鎖,等待競(jìng)爭(zhēng)鎖的線程一直自旋,即CPU一直空轉(zhuǎn),資源浪費(fèi)在毫無(wú)意義的地方,所以一般會(huì)限制自旋次數(shù)。

最后來(lái)說(shuō)自旋鎖的實(shí)現(xiàn),實(shí)現(xiàn)自旋鎖可以基于C A S實(shí)現(xiàn),先定義lockValue對(duì)象默認(rèn)值1,1代表鎖資源空閑,0代表鎖資源被占用,代碼如下

public class SpinLock {
    
    //lockValue 默認(rèn)值1
    private AtomicInteger lockValue = new AtomicInteger(1);
    
    //自旋獲取鎖
    public void lock(){

        // 循環(huán)檢測(cè)嘗試獲取鎖
        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變量,AtomicIntegerJava基于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)

java中CAS是什么

從上圖可以看出,只有tryLock成功的線程(lockValue更新為0),才會(huì)執(zhí)行代碼塊,其他線程個(gè)tryLock自旋等待lockValue被更新成1tryLock成功的線程執(zhí)行unLocklockValue更新為1),自旋的線程才會(huì)tryLock成功。

ABA問(wèn)題

C A S需要檢查待更新的內(nèi)存值有沒(méi)有被修改,如果沒(méi)有則更新,但是存在這樣一種情況,如果一個(gè)值原來(lái)是A,變成了B,然后又變成了A,在C A S檢查的時(shí)候會(huì)發(fā)現(xiàn)沒(méi)有被修改。

假設(shè)有兩個(gè)線程,線程1讀取到內(nèi)存值A,線程1時(shí)間片用完,切換到線程2,線程2也讀取到了內(nèi)存值A,并把它修改為B值,然后再把B值還原到A值,簡(jiǎn)單說(shuō),修改次序是A->B->A,接著線程1恢復(fù)運(yùn)行,它發(fā)現(xiàn)內(nèi)存值還是A,然后執(zhí)行C A S操作,這就是著名的ABA問(wèn)題,但是好像又看不出什么問(wèn)題。

只是簡(jiǎn)單的數(shù)據(jù)結(jié)構(gòu),確實(shí)不會(huì)有什么問(wèn)題,如果是復(fù)雜的數(shù)據(jù)結(jié)構(gòu)可能就會(huì)有問(wèn)題了(使用AtomicReference可以把C A S使用在對(duì)象上),以鏈表數(shù)據(jù)結(jié)構(gòu)為例,兩個(gè)線程通過(guò)C A S去刪除頭節(jié)點(diǎn),假設(shè)現(xiàn)在鏈表有A->B節(jié)點(diǎn)

java中CAS是什么

  • 線程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問(wèn)題也非常簡(jiǎn)單,只要追加版本號(hào)即可,每次改變時(shí)加1,即A —> B —> A,變成1A —> 2B —> 3A,在Java中提供了AtomicStampedRdference可以實(shí)現(xiàn)這個(gè)方案(面試只要問(wèn)了C A S,就一定會(huì)問(wèn)ABA,這塊一定要搞明白)。

感謝各位的閱讀!關(guān)于“java中CAS是什么”這篇文章就分享到這里了,希望以上內(nèi)容可以對(duì)大家有一定的幫助,讓大家可以學(xué)到更多知識(shí),如果覺(jué)得文章不錯(cuò),可以把它分享出去讓更多的人看到吧!

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI