1. 程式人生 > >深入理解CAS

深入理解CAS

前言

首先這篇文章是對前文深入理解ConcurrentHashMap中提到的CAS概念做補充的。其次是講解CAS理論,我也看過很多關於CAS的部落格,重複性,概念性都太強了,我要做的與眾不同,我會把我所理解的用通俗易懂的語言描述出來的。

正文

什麼是CAS

CAS(比較與交換,Compare and swap)是一種有名的無鎖演算法。

CAS工作原理

CAS指令需要有3個運算元,分別是記憶體為止(在Java中可以簡單理解為變數的記憶體地址,用V表示)、舊的預期值(用A表示)和新值(用B表示)。CAS指令執行時,當且僅當V符合舊預期值A時,處理器用新值B更新V的值,否則他就不執行更新,但是無論是否更新了V的值,都會返回V的舊值,上述的處理過程是一個原子操作。

如何使用CAS操作來避免阻塞同步

下面的程式碼主要是使用了20個執行緒進行自增10000次來證明原子性.執行結果是:20000

public static AtomicInteger race = new AtomicInteger(0);
    private static final int THREADS_COUNT = 20;

    public static void increase() {
        race.incrementAndGet();
    }
    @Test
    public void atomicTest() {

        Thread[] threads = new Thread[THREADS_COUNT];
        for (int i = 0; i < THREADS_COUNT; i++) {
            threads[i] = new Thread(new Runnable() {
                @Override
                public void run() {
                    for (int i = 0; i < 10000; i++) {
                        increase();
                    }
                }
            });
            threads[i].start();
        }
        while (Thread.activeCount() > 1)
            Thread.yield();
        System.out.println(race);
    }

我們使用了AtomicInteger了,程式輸出正確結果,一切都要歸功於incrementAndGet()方法的原子性,該方法無限迴圈,不斷嘗試將一個一個比當前值大1的新值賦給自己,如果失敗了那說明在執行“獲取-設定“操作的時候值已經有了修改,於是再次迴圈進行下一次操作,只帶設定成功為止,它的原理實現其實非常簡單。程式碼如下:

/**
     * Atomically increments by one the current value.
     *
     * @return the updated value
     */
    public final int incrementAndGet() {
        return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
    }
public final int getAndAddInt(Object var1, long var2, int var4) {
        int var5;
        do {
            var5 = this.getIntVolatile(var1, var2);
        } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));

        return var5;
    }

上面的核心程式碼都在Unsafe.class  大家可以自己進去看一看

CAS缺點

如果一個變數V初次讀取的時候是A值,並且在準備賦值的時候檢查到它仍然是A值,那我們就能說它值沒有被其他執行緒改變過嗎?

如果在這段期間它的值曾經改成了B,後來又改成了A,那麼CAS操作就會誤認為它沒有改變過,這個漏洞稱為“ABA”問題。J.U.C包為了解決這個問題,提供了一個帶有標記的原子引用類“AtomicStampedReference”,它可以通過控制變數值的版本來保證CAS的正確性,如果需要解決ABA問題,改用傳統的互斥同步(典型的就是synchronized 和Lock)可能會比原子類更高效。

總結:Unsafe類是CAS實現的核心。 從名字可知,這個類標記為不安全的,CAS會使得程式設計比較負責,但是由於其優越的效能優勢,以及天生免疫死鎖(根本就沒有鎖,當然就不會有執行緒一直阻塞了),更為重要的是,使用無鎖的方式沒有所競爭帶來的開銷,也沒有執行緒間頻繁排程帶來的開銷,他比基於鎖的方式有更優越的效能,所以在目前被廣泛應用,我們在程式設計時也可以適當的使用.不過由於CAS編碼確實稍微複雜,而且jdk作者本身也不希望你直接使用unsafe,所以如果不能深刻理解CAS以及unsafe還是要慎用,使用一些別人已經實現好的無鎖類或者框架就好了。

附:

JVM中的CAS

堆中物件的分配

簡單的說new出來一個物件之前大小其實已經固定,把他放到堆裡以什麼形式儲存的呢?

由於再給一個物件分配記憶體的時候不是原子性的操作,至少需要以下幾步:查詢空閒列表、分配記憶體、修改空閒列表等等,這是不安全的。解決併發時的安全問題也有兩種策略:

  1. CAS 

實際上虛擬機器採用CAS配合上失敗重試的方式保證更新操作的原子性,原理和上面講的一樣。

  1. TLAB 

如果使用CAS其實對效能還是會有影響的,所以JVM又提出了一種更高階的優化策略:每個執行緒在Java堆中預先分配一小塊記憶體,稱為本地執行緒分配緩衝區(TLAB),執行緒內部需要分配記憶體時直接在TLAB上分配就行,避免了執行緒衝突。只有當緩衝區的記憶體用光需要重新分配記憶體的時候才會進行CAS操作分配更大的記憶體空間。 

虛擬機器是否使用TLAB,可以通過-XX:+/-UseTLAB引數來進行配置(jdk5及以後的版本預設是啟用TLAB的)。

注:對本文有異議或不明白的地方微信探討,wx:15524579896