1. 程式人生 > >併發系列(四)-----CAS

併發系列(四)-----CAS

一 簡介

 保證Java中的原子操做方式有兩種方式
  1 加鎖(可以理解悲觀鎖機制)
  2 CAS(可以理解為樂觀鎖機制)
  CAS全稱是Compare and Swap 即比較並替換。在JDK中許多地方都可以看到它的身影,比如AQS同步元件,Atomic原子類操作等等都是以CAS實現的。其中java.util.concurrent 中的許多概念源自 Doug Lea 的 util.concurrent 庫,而Doug lea大神在同步元件中大量使用使用CAS技術鬼斧神工地實現了Java多執行緒的併發操作。


 二 CAS原理

在CAS中有三個引數:記憶體值V、舊的預期值A,要更新的值B。更新一個變數的時候,只有當變數的預期值A和記憶體地址V當中的實際值相同時,才會將記憶體地址V對應的值修改為B。
來看一下Atomic包的原始碼中是如何使用的CAS,並分析它的原理
AtomicInteger的成員變數

     // setup to use Unsafe.compareAndSwapInt for updates 
     private static final Unsafe unsafe = Unsafe.getUnsafe();
     private static final long valueOffset;
     static {
        try {
            valueOffset = unsafe.objectFieldOffset
                (AtomicInteger.class.getDeclaredField("value"));
        } catch (Exception ex) { throw new Error(ex); }
    }
    private volatile int value;

AtomicInteger的成員變數進行說明
1 Unsafe 
Java語言不像C,C++那樣可以直接訪問底層作業系統,但是JVM為我們提供了一個後門,這個後門就是unsafe。unsafe為我們提供了硬體級別的原子操作 。
2 valueOffset
至於valueOffset物件,是通過unsafe.objectFieldOffset方法得到,所代表的是AtomicInteger物件value成員變數在記憶體中的偏移量。我們可以簡單地把valueOffset理解為value變數的記憶體地址。
3 value
被volatile所修飾被volatile的特點就不在說明了上一篇文章已經提到過了。
AtomicInteger的compareAndSet()

    /**
     * Atomically sets the value to the given updated value
     * if the current value {@code ==} the expected value.
     *
     * @param expect the expected value
     * @param update the new value
     * @return {@code true} if successful. False return indicates that
     * the actual value was not equal to the expected value.
     */
    public final boolean compareAndSet(int expect, int update) {
        return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
    }
    //Unsafe.class
    public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);


這個方法是修改值的方法,引數要求傳兩個引數期望值和要更新的值,如果更新成功的話返回true,反之亦然。在方法內部呼叫的是Unsafe的compareAndSwapInt,傳而unsafe的compareAndSwapInt方法引數包括了這三個基本元素:valueOffset引數代表了V,expect引數代表了A,update引數代表了B。

正是unsafe的compareAndSwapInt方法保證了Compare和Swap操作之間的原子性操作。


三 CAS的問題


 1.ABA問題
因為CAS需要在操作值的時候,檢查值有沒有發生變化,如果沒有發生變化則更新,但是如果一個值原來是A,變成了B,又變成了A,那麼使用CAS進行檢查時會發現它的值沒有發生變化,但是實際上卻變化了。ABA問題的解決思路就是使用版本號。在變數前面追加上版本號,每次變數更新的時候把版本號加1,那麼A→B→A就會變成1A→2B→3A。從Java 1.5開始,JDK的Atomic包裡提供了一個類AtomicStampedReference來解決ABA問題。這個類compareAndSet方法的作用是首先檢查當前引用是否等於預期引用,並且檢查當前標誌是否等於預期標誌,如果全部相等,則以原子方式將該引用和該標誌的值設定為給定的更新值。
2.迴圈時間長CPU開銷大
自旋CAS如果長時間不成功,會給CPU帶來非常大的執行開銷
3.只能保證一個共享變數的原子操做