【面試題】能聊聊你對CAS的理解以及其底層實現原理可以嗎?

裝兔子的貓發表於2020-12-06

當多個執行緒要訪問同一個資料時,包括取值、詢問、修改時,必然會出現多執行緒併發安全問題

public class MyObject{
    int i = 0;
    public synchronized void increment(){
        //同一時間,只有一個執行緒可以進入當前方法
        //在一個物件例項的方法上加synchronized
        i++;
    }
}

呼叫:
MyObject mo = new MyObject();
mo.increment();

這種加鎖的方式是序列化,執行效率不高。多執行緒情況下,需要排隊執行increment方法

 

使用併發包下的AtomicInteger類(底層基於CAS來進行實現)

public class MyObject{
    AtomicInteger i = new AtomicInteger(0);
    public  void increment(){
        //多個執行緒此時來執行這段程式碼
        //不需要synchronized加鎖,也是執行緒安全的
        i.incrementAndGet();
    }
}

CAS全稱 compare and set,CAS操作是原子的,底層會進行硬體之別的上鎖,中間是不可以被打斷的

(1)先讀取當前值,假設i=0

(2)i++

(3)嘗試設定i=1:先對比預期值是否為0,如果為0則設定i=1;

cas(v,expected,newValue)

    if(v==expected)

        v = newValue;

執行緒1與執行緒2同時呼叫incrementAndSet方法,只有一個執行緒能完成CAS。

CAS在底層的硬體級別保證一定是原子的,同一時間只有一個執行緒可以執行CAS,先比較再設定,其他執行緒的CAS同時間去執行,此時會失敗。

假設:執行緒1成功CAS,執行緒2在CAS過程中發現值不對,會重新讀取當前值,再次嘗試CAS

AtomicXX類底層的實現原理實際上是CAS

 

【評論區】

CAS雖然高效的解決了原子操作問題,但仍然存在三大問題

1.ABA問題:如果變數V初次讀取的時候值是A,後來變成了B,然後又變成了A,你本來期望的值是第一個A才會設定新值,第二個A跟期望不符合,但卻也能設定新值。針對這種情況,java併發包中提供了一個帶有標記的原子引用類AtomicStampedReference,它可以通過控制變數值的版本號來保證CAS的正確性,比較兩個值的引用是否一致,如果一致,才會設定新值。但使用AtomicStampedReference就可以很好的解決這個問題。

2.無限迴圈問題(自旋):看原始碼可知,Atomic類設定值的時候會進入一個無限迴圈,只要不成功,就會不停的迴圈再次嘗試。在高併發時,如果大量執行緒頻繁修改同一個值,可能會導致大量執行緒執行compareAndSet()方法時需要迴圈N次才能設定成功,即大量執行緒執行一個重複的空迴圈(自旋),造成大量開銷。解決無限迴圈問題可以使用java8中的LongAdder,分段CAS和自動分段遷移

3.多變數原子問題:只能保證一個共享變數的原子操作。一般的Atomic類,只能保證一個變數的原子性,但如果是多個變數呢?可以用AtomicReference,這個是封裝自定義物件的,多個變數可以放一個自定義物件裡,然後他會檢查這個物件的引用是不是同一個。如果多個執行緒同時對一個物件變數的引用進行賦值,用AtomicReference的CAS操作可以解決併發衝突問題。 但是如果遇到ABA問題,AtomicReference就無能為力了,需要使用AtomicStampedReference來解決。

 

 

 

 

 

相關文章