Java CAS機制詳解

CAS目的:
html

在多線程中爲了保持數據的準確性,避免多個線程同時操做某個變量,不少狀況下利用關鍵字synchronized實現同步鎖,使用synchronized關鍵字修可使操做的線程排隊等待運行,能夠說是一種悲觀策略,認爲線程會修改數據,因此開始就把持有鎖的線程鎖住,其餘線程只能是掛起狀態,等待鎖的釋放,因此同步鎖帶來了問題:java

主要的效率問題:在線程執行的時候,得到鎖的線程在運行,其餘被掛起的線程只能等待着持有鎖的線程釋放鎖纔有機會運行(如今JVM可能根據持有鎖的時間來操做線程是不是被掛起仍是自旋等待),在效率上都浪費在等待上。極可能這種鎖是沒有必要的,其餘線程沒有修改數據。在不少的線程切換的時候,因爲有同步鎖,就要涉及到鎖的釋放,加鎖,這又是一個很大的時間開銷。這裏涉及到操做系統上的知識,關於線程之間的切換(被掛起和恢復)中間還要經歷中斷,時間片等等。算法

上面說了這麼多,如今咱們追求的是一種效率高,還要保證數據的安全的一種方法。
編程

與鎖(阻塞機制)的方式相比有一種更有效地方法,非阻塞機制,同步鎖帶來了線程執行時候之間的阻塞,而這種非阻塞機制在多個線程競爭同一個數據的時候不會發生阻塞的狀況,這樣在時間上就能夠節省出不少的時間。安全

想到這裏,知道volatile的可能會想到用volatile,使用volatile不會形成阻塞,volatile保證了線程之間的內存可見性和程序執行的有序性能夠說已經很好的解決了上面的問題,可是一個很重要的問題就是,volatile不能保證原子性,對於複合操做,例如i++這樣的程序包含三個原子操做:取指,增長,賦值。在《Java併發編程實戰》這本書上有這樣的一句話:變量的新值依賴於舊值時就不能使用volatile變量。實際上就是說的相似於i++這樣的操做。具體詳見:volatile關鍵字解析
多線程

什麼是CAS:
併發

如今採起的是CAS(Compare And Swap比較和交換)解決了volatile不能保證原子性。CAS一般比鎖定要快得多,但這取決於爭用的程度。由於若是讀取和比較之間的值發生變化,CAS可能會強制重試,因此不能說某個方法就是絕對的好。CAS的主要問題是編程比鎖定更困難。還好jdk提供了一些類用於完成基本的操做。this

CAS主要包含三個操做數,內存位置V,進行比較的原值A,和新值B。當位置V的值與A相等時,CAS纔會經過原子方式用新值B來更新V,不然不會進行任何操做。不管位置V的值是否等於A,都將返回V原有的值。通俗點說:我認爲V地址的值應該是A,若是是,V地址的值更新爲B,不然不修改並告訴V的值實際爲多少(不管如何這個值都會通知到V)。上面說到了同步鎖 是一種悲觀策略,CAS是一種樂觀策略,每次都開放本身,不用擔憂其餘線程會修改變量等數據,若是其餘線程修改了數據,那麼CAS會檢測到並利用算法從新計算。CAS也是同時容許一個線程修改變量,其餘的線程試圖修改都將失敗,可是相比於同步鎖,CAS對於失敗的線程不會將他們掛起,他們下次仍能夠參加競爭,這也就是非阻塞機制的特色。spa

下面用代碼簡單的實現CAS原理:操作系統

/**
 * Created with IDEA
 *
 * @author DuzhenTong
 * @Date 2018/2/1
 * @Time 11:52
 */
public class SimpleCAS {

    private int value;

    public SimpleCAS(int value) {
        this.value = value;
    }

    public synchronized int get(){
        return value;
    }

    public synchronized int compareAndSwap(int expectedValue, int newValue){
        int oldValue = value;//獲取舊值
        if(oldValue == expectedValue){//若是指望值與當前V位置的值相同就給予新值
            value = newValue;
        }
        return oldValue;//返回V位置原有的值
    }

    public synchronized boolean compareAndSet(int expectedValue, int newValue){
        return (expectedValue == compareAndSwap(expectedValue, newValue));
    }

    public static void main(String[] args) {
        SimpleCAS simpleCAS = new SimpleCAS(3);
        simpleCAS.compareAndSet(5, 10);
        System.out.println(simpleCAS.get());//3

        SimpleCAS simpleCAS1 = new SimpleCAS(1);
        simpleCAS1.compareAndSet(1, 6);
        System.out.println(simpleCAS1.get());//6
    }

}

從運行結果能夠看出代碼的原理:設置一個初始值(內存位置),指望值和新值進行比較,若是指望值和初始值一致,返回新值,不然返回初始值。意思是你在修改在一個變量A,假如它原來的值是3,因此你預期它是3,若是在你修改的時候,它被別的線程更新爲5,那麼就不符合你的預期,你的修改也不會生效

從Java5開始引入了底層的支持,在這以前須要開發人員編寫相關的代碼才能夠實現CAS。在原子變量類Atomic***中(例如AtomicInteger、AtomicLong)能夠看到CAS操做的代碼,在這裏的代碼都是調用了底層(核心代碼調用native修飾的方法)的實現方法。在AtomicInteger源碼中能夠看getAndSet方法和compareAndSet方法之間的關係,compareAndSet方法調用了底層的實現,該方法能夠實現與一個volatile變量的讀取和寫入相同的效果。在前面說到了volatile不支持例如i++這樣的複合操做,在Atomic***中提供了實現該操做的方法。JVM對CAS的支持經過這些原子類(Atomic***)暴露出來,供咱們使用。

CAS帶來的問題:

ABA問題:CAS在操做的時候會檢查變量的值是否被更改過,若是沒有則更新值,可是帶來一個問題,最開始的值是A,接着變成B,最後又變成了A。通過檢查這個值確實沒有修改過,由於最後的值仍是A,可是實際上這個值確實已經被修改過了。爲了解決這個問題,在每次進行操做的時候加上一個版本號,每次操做的就是兩個值,一個版本號和某個值,A——>B——>A問題就變成了1A——>2B——>3A。在jdk中提供了AtomicStampedReference類解決ABA問題,用Pair這個內部類實現,包含兩個屬性,分別表明版本號和引用,在compareAndSet中先對當前引用進行檢查,再對版本號標誌進行檢查,只有所有相等才更新值。

時間問題:看起來CAS比鎖的效率高,從阻塞機制變成了非阻塞機制,減小了線程之間等待的時間。每一個方法不能絕對的比另外一個好,在線程之間競爭程度大的時候,若是使用CAS,每次都有不少的線程在競爭,而鎖能夠避免這些情況,相反的狀況,若是線程之間競爭程度小,使用CAS是一個很好的選擇。

相關文章
相關標籤/搜索