1. 程式人生 > >Java中的CAS

Java中的CAS

CAS全稱 Compare And Swap(比較與交換),是一種無鎖演算法。在不使用鎖(沒有執行緒被阻塞)的情況下實現多執行緒之間的變數同步,java.util.concurrent包中的原子類就是通過CAS來實現了樂觀鎖。

基本介紹

CAS 操作包含三個運算元 —— 記憶體位置(V)、預期原值(A)和新值(B)。如果記憶體位置的值與預期原值相匹配,那麼處理器會自動將該位置值更新為新值。否則,處理器不做任何操作。

使用場景

有的時候我們需要對變數進行操作,如果是多執行緒,則有可能達不到我們的預期結果,Synchronized等關鍵字,可以解決問題,但是Synchronized關鍵字會讓沒有得到鎖資源的執行緒進入BLOCKED

狀態,而後在爭奪到鎖資源後恢復為RUNNABLE狀態,這個過程中涉及到作業系統使用者模式核心模式的轉換,代價比較高。這個時候我們就可以使用Atomic的一些原子操作來進行。

public class CASTest {

    public static volatile int count = 0;

    public static AtomicInteger atomicCount = new AtomicInteger();

    public static void main(String[] args) throws InterruptedException {

        for (int i = 0; i < 2; i++) {
            new Thread(() -> {
                for (int j = 0; j < 10000; j++) {
                    count++;
                    atomicCount.incrementAndGet();
                }
                System.out.println("count++ end!");
            }).start();
        }

        Thread.sleep(2000);
        System.out.println(count);
        System.out.println(atomicCount.get());
    }
}

來看一下這個的程式碼輸出

count++ end!
count++ end!
18003
20000

可以看到volatile也沒有辦法保證運算的原子性。AtomicInteger使用CAS操作,可以保證運算的原子性。

AtomicInteger的實現原理

我們可以看一下AtomicInteger中incrementAndGet()的原始碼

public final int incrementAndGet() {
    return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
}

其是通過Unsafe來實現的,我們進入Unsafe中的getAndInt()方法中

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;
}

compareAndSwapInt是本地的方法,是通過CPU的cmpxchg指令,去比較暫存器中的 A 和 記憶體中的值 V。如果相等,就把要寫入的新值 B 存入記憶體中。如果不相等,就將記憶體值 V 賦值給暫存器中的值 A。然後通過Java程式碼中的while迴圈再次呼叫cmpxchg指令進行重試,直到設定成功為止。

CAS存在的問題

  1. ABA問題CAS需要在操作值的時候檢查記憶體值是否發生變化,沒有發生變化才會更新記憶體值。但是如果記憶體值原來是A,後來變成了B,然後又變成了A,那麼CAS進行檢查時會發現值沒有發生變化,但是實際上是有變化的。ABA問題的解決思路就是在變數前面新增版本號,每次變數更新的時候都把版本號加一,這樣變化過程就從“A-B-A”變成了“1A-2B-3A”。JDK從1.5開始提供了AtomicStampedReference類來解決ABA問題。

  2. 併發高迴圈時間長的時候開銷大CAS操作如果長時間不成功,會導致其一直自旋,給CPU帶來非常大的開銷。

  3. 只能保證一個共享變數的原子操作對一個共享變數執行操作時,CAS能夠保證原子操作,但是對多個共享變數操作時,CAS是無法保證操作的原子性的。

    Java從1.5開始JDK提供了AtomicReference類來保證引用物件之間的原子性,可以把多個變數放在一個物件裡來進行CAS操作。

參考

https://tech.meituan.com/2018/11/15/java-lock.html

歡迎關注公眾號:蜜蜂技術巢