徹底弄懂CAS(比較並交換)

什麼是CAS?

我們在使用鎖時,線程獲取鎖是一種悲觀鎖策略,即假設每一次執行臨界區代碼都會產生衝突,所以當前線程獲取到鎖的時候同時也會阻塞其他線程獲取該鎖。而CAS操作(又稱爲無鎖操作)是一種樂觀鎖策略,它假設所有線程訪問共享資源的時候不會出現衝突,既然不會出現衝突自然而然就不會阻塞其他線程的操作。因此,線程就不會出現阻塞停頓的狀態。那麼,如果出現衝突了怎麼辦?無鎖操作是使用CAS(compare and swap)又叫做比較交換來鑑別線程是否出現衝突,出現衝突就重試當前操作直到沒有衝突爲止。

CAS的操作過程

CAS比較交換的過程可以通俗的理解爲CAS(V,O,N),包含三個值分別爲:V 內存地址存放的實際值;O 預期的值(舊值);N 更新的新值。當V和O相同時,也就是說舊值和內存中實際的值相同表明該值沒有被其他線程更改過,即該舊值O就是目前來說最新的值了,自然而然可以將新值N賦值給V。反之,V和O不相同,表明該值已經被其他線程改過了則該舊值O不是最新版本的值了,所以不能將新值N賦給V,返回V即可。當多個線程使用CAS操作一個變量是,只有一個線程會成功,併成功更新,其餘會失敗。失敗的線程會重新嘗試,當然也可以選擇掛起線程
CAS的實現需要硬件指令集的支撐,在JDK1.5後虛擬機纔可以使用處理器提供的CMPXCHG指令實現。

//var1 當前的對象,誰調用了這個方法,比如說count
//var2 當前的值,比如說要執行2+1的操作,當前值就是2  var4 就是1
//var5 調用底層的方法而得到底層的當前的值,也就是當前主內存中的值,
//如果沒有別的線程去處理當前的count這個對象,現在var5==var2,如果沒更改了則不一樣了

    public final int getAndAddInt(Object var1, long var2, int var4) {
        int var5;
        do {
            var5 = this.getIntVolatile(var1, var2);
            //compareAndSwapInt想要達到的目的是:如果當前這個對象的當前的值如果等於
            //底層傳過來的這個值,則把它更新成var5+var4
        } while (!this.compareAndSwapInt(var1, var2, var5, var5 + var4));

        return var5;
    }
//當我們執行的時間,我們取出的值是var2  第一次取出的變量var5也是var2,
//但是當我們執行更新操作的時間,主內存的值可能被別的線程更改 ,這時重新取出var5,
//var2 也會從變量中去最新的  再次執行在這裏插入代碼片

Synchronized VS CAS

元老級的Synchronized(未優化前)最主要的問題是:在存在線程競爭的情況下會出現線程阻塞和喚醒鎖帶來的性能問題,因爲這是一種互斥同步(阻塞同步)。而CAS並不是武斷的間線程掛起,當CAS操作失敗後會進行一定的嘗試,而非進行耗時的掛起喚醒的操作,因此也叫做非阻塞同步。這是兩者主要的區別。

CAS的問題

ABA問題

因爲CAS會檢查舊值有沒有變化,這裏存在這樣一個有意思的問題。比如一箇舊值A變爲了成B,然後再變成A,剛好在做CAS時檢查發現舊值並沒有變化依然爲A,但是實際上的確發生了變化。解決方案可以沿襲數據庫中常用的樂觀鎖方式,添加一個版本號可以解決。原來的變化路徑A->B->A就變成了1A->2B->3C。

自旋時間過長

使用CAS時非阻塞同步,也就是說不會將線程掛起,會自旋(無非就是一個死循環)進行下一次嘗試,如果這裏自旋時間過長對性能是很大的消耗。如果JVM能支持處理器提供的pause指令,那麼在效率上會有一定的提升。

只能保證一個共享變量的原子操作

當對一個共享變量執行操作時CAS能保證其原子性,如果對多個共享變量進行操作,CAS就不能保證其原子性。有一個解決方案是利用對象整合多個共享變量,即一個類中的成員變量就是這幾個共享變量。然後將這個對象做CAS操作就可以保證其原子性。atomic中提供了AtomicReference來保證引用對象之間的原子性。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章