CAS解析

CAS:compare and swap,也有的叫做 compare and set;意思都差不多,翻譯過來就是比較并交換或者比較并設值。

CAS包含三個值,內存地址(V),預期值(A),新值(B)。先比較內存地址的值和預期的值是否相等,如果相等,就將新值賦在內存地址上,否則,不做任何處理。這種是樂觀鎖的思想。

CAS-1

源碼解析

CAS操作在JUC中大量用到,在解析AQS那章中,我們也有提到。再回頭看一下AQS中CAS的操作

protected final boolean compareAndSetState(int expect, int update) {
        return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
    }

這里是對AQS中state變量進行的CAS操作,要知道,很多同步類都是通過這個變量來實現線程安全的,所以在AQS中,首先要保證對state的賦值是線程安全的。

在java中,不論什么操作,只要他還是在Java api級別,就不能保證他一定是線程安全的,除非這個操作調用系統資源來支持。這里保證state線程安全是通過unsafe中的compareAndSwapInt方法來實現的,里面的參數stateOffset就是內存地址(V),expect是預期值(A),新值(B)。在unsafe中,大部分方法都是native的,而且unsafe可以直接操作系統的內存資源,不受jvm限制,通過它來保證線程安全,應該是穩了,但是unsafe類官方是不建議用的,咱們平時開發大部分時候也用不著,需要的操作,jdk已經幫我們封裝好了。

CAS的問題

CAS雖然看起來很完美,可以在原子級別保證一個變量的線程安全,但是它有時候也會出問題,比如著名的ABA問題。

ABA問題:我們都知道CAS操作是先比較A的預期值和內存地址中的值是否相同,如果相同就認為此時沒有其他線程修改A值,但是一定是這樣嗎?假如一個線程讀取到A值,此時有另外一個線程將A值改成了B,然后又將B改回了A,這時比較A和預期值是相同的,就認為A值沒有被改變過。為了解決ABA的問題,可以使用版本號,每次修改變量,都在這個變量的版本號上加1,這樣,剛剛A->B->A,雖然A的值沒變,但是它的版本號已經變了,再判斷版本號就會發現此時的A已經被別人偷偷改過了。

CAS還有一個性能問題,在大部分使用CAS的時候,都是配合自旋來使用,這里的自旋,你可以理解為for(;;)這樣的無限循環,我們在jdk源碼中找一處來看看

public final int getAndSet(int newValue) {
        for (;;) {
            int current = get();
            if (compareAndSet(current, newValue))
                return current;
        }
    }

這AtomicInteger類中getAndSet方法,這是jdk1.7中的代碼,在1.8中換成了另外一種寫法,意思都是自旋加CAS,jdk1.7中自旋在AtomicInteger里,而jdk1.8調用了unsafe中getAndSetInt方法,在這個方法中自旋,有興趣的話可以去看看1.8中的源碼。

平時我們開發過程中,如果碰到系統蹦了,大部分人可能第一時間就會想到代碼里是不是出現死循環了,當出現死循環時,會占用系統大量資源,造成系統崩潰。在這里我們看到源碼中的自旋就是當CAS成功時,才會return。當然也不會出現CAS一直失敗的情況,那幾率也太小了。因此CAS帶來的性能問題也是需要考慮的。但是從某種意思上來說,這個自旋也是CAS的優勢,自旋算是一種非阻塞算法,相對于其他阻塞算法而已,非阻塞是不需要cpu切換時間片保存上下文的,節省了大量性能消耗。

結論

CAS是一種無鎖解決并發問題的手段,解決了鎖引起線程切換帶來的性能問題。

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容