1. 程式人生 > >CAS演算法原理分析

CAS演算法原理分析

在java高併發多執行緒學習中,CAS演算法--無所鎖演算法是一種解決高併發的使用的樂觀鎖思想的機制,轉載別人的文章作為知識積累:


轉自:http://www.360doc.com/content/11/0914/16/7656248_148221200.shtml

更加深入的研究參看:


在JDK 5之前Java語言是靠synchronized關鍵字保證同步的,這會導致有鎖(後面的章節還會談到鎖)。

鎖機制存在以下問題:

(1)在多執行緒競爭下,加鎖、釋放鎖會導致比較多的上下文切換和排程延時,引起效能問題。

(2)一個執行緒持有鎖會導致其它所有需要此鎖的執行緒掛起。

(3)如果一個優先順序高的執行緒等待一個優先順序低的執行緒釋放鎖會導致優先順序倒置,引起效能風險。

volatile是不錯的機制,但是volatile不能保證原子性。因此對於同步最終還是要回到鎖機制上來。

獨佔鎖是一種悲觀鎖,synchronized就是一種獨佔鎖,會導致其它所有需要鎖的執行緒掛起,等待持有鎖的執行緒釋放鎖。而另一個更加有效的鎖就是樂觀鎖。所謂觀鎖就是,每次不加鎖而是假設沒有衝突而去完成某項操作,如果因為衝突失敗就重試,直到成功為止。

CAS 操作

上面的樂觀鎖用到的機制就是CAS,Compare and Swap。

CAS有3個運算元,記憶體值V,舊的預期值A,要修改的新值B。當且僅當預期值A和記憶體值V相同時,將記憶體值V修改為B,否則什麼都不做。

非阻塞演算法 (nonblocking algorithms)

一個執行緒的失敗或者掛起不應該影響其他執行緒的失敗或掛起的演算法。

現代的CPU提供了特殊的指令,可以自動更新共享資料,而且能夠檢測到其他執行緒的干擾,而 compareAndSet() 就用這些代替了鎖定。

拿出AtomicInteger來研究在沒有鎖的情況下是如何做到資料正確性的。

private volatile int value;

首先毫無以為,在沒有鎖的機制下可能需要藉助volatile原語,保證執行緒間的資料是可見的(共享的)。這樣才獲取變數的值的時候才能直接讀取。

public final int get() {
        return value;
    }

然後來看看++i是怎麼做到的。

public final int incrementAndGet() {
    for (;;) {
        int current = get();
        int next = current + 1;
        if (compareAndSet(current, next))
            return next;
    }
}

在這裡採用了CAS操作,每次從記憶體中讀取資料然後將此資料和+1後的結果進行CAS操作,如果成功就返回結果,否則重試直到成功為止。

而compareAndSet利用JNI來完成CPU指令的操作。

public final boolean compareAndSet(int expect, int update) {   
    return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
    }

整體的過程就是這樣子的,利用CPU的CAS指令,同時藉助JNI來完成Java的非阻塞演算法。其它原子操作都是利用類似的特性完成的。

而整個J.U.C都是建立在CAS之上的,因此對於synchronized阻塞演算法,J.U.C在效能上有了很大的提升。

CAS看起來很爽,但是會導致“ABA問題”。

CAS演算法實現一個重要前提需要取出記憶體中某時刻的資料,而在下時刻比較並替換,那麼在這個時間差類會導致資料的變化

比如說一個執行緒one從記憶體位置V中取出A,這時候另一個執行緒two也從記憶體中取出A,並且two進行了一些操作變成了B,然後two又將V位置的資料變成A,這時候執行緒one進行CAS操作發現記憶體中仍然是A,然後one操作成功。儘管執行緒one的CAS操作成功,但是不代表這個過程就是沒有問題的。如果連結串列的頭在變化了兩次後恢復了原值,但是不代表連結串列就沒有變化。因此前面提到的原子操作AtomicStampedReference/AtomicMarkableReference就很有用了。這允許一對變化的元素進行原子操作。

================================================================================================

維基百科:

In computer science, the compare-and-swap CPU instruction ("CAS") (or the Compare & Exchange - CMPXCHG instruction in the x86 and Itanium architectures) is a special instruction that atomically (regarding intel x86, lock prefix should be there to make it really atomic) compares the contents of a memory location to a given value and, only if they are the same, modifies the contents of that memory location to a given new value. This guarantees that the new value is calculated based on up-to-date information; if the value had been updated by another thread in the meantime, the write would fail. The result of the operation must indicate whether it performed the substitution; this can be done either with a simple Boolean response (this variant is often called compare-and-set), or by returning the value read from the memory location (not the value written to it). Compare-and-Swap (and Compare-and-Swap-Double) has been an integral part of the IBM 370(and all successor) architectures since 1970. The operating systems which run on these architectures make extensive use of Compare-and-Swap (and Compare-and-Swap-Double) to facilitate process (i.e., system and user tasks) and processor (i.e., central processors) parallelism while eliminating, to the greatest degree possible, the "disabled spin locks" which were employed in earlier IBM operating systems. In these operating systems, new units of work may be instantiated "globally", into the Global Service Priority List, or "locally", into the Local Service Priority List, by the execution of a single Compare-and-Swap instruction. This dramatically improved the responsiveness of these operating systems.

===================================================

總結:CAS是硬體CPU提供的元語,它的原理:我認為位置 V 應該包含值 A;如果包含該值,則將 B 放到這個位置;否則,不要更改該位置,只告訴我這個位置現在的值即可。

Java併發庫中的AtomicXXX類均是基於這個元語的實現,以AtomicInteger為例:

publicfinalint incrementAndGet() { for (;;) { int current = get(); int next = current +1; if (compareAndSet(current, next)) return next; } } publicfinalboolean compareAndSet(int expect, int update) { return unsafe.compareAndSwapInt(this, valueOffset, expect, update); }

其中,unsafe.compareAndSwapInt()是一個native方法,正是呼叫CAS元語完成該操作。