什么是 CAS(比較并交換-樂觀鎖機制-鎖自旋)
概念及特性
CAS(Compare And Swap/Set)比較并交換,CAS 算法的過程是這樣:它包含 3 個參數(shù) CAS(V,E,N)。V 表示要更新的變量(內(nèi)存值),E 表示預(yù)期值(舊的),N 表示新值。當(dāng)且僅當(dāng) V 值等于 E 值時,才會將 V 的值設(shè)為 N,如果 V 值和 E 值不同,則說明已經(jīng)有其他線程做了更新,則當(dāng)前線程什么都不做。最后,CAS 返回當(dāng)前 V 的真實值。
CAS 操作是抱著樂觀的態(tài)度進行的(樂觀鎖),它總是認(rèn)為自己可以成功完成操作。當(dāng)多個線程同時使用 CAS 操作一個變量時,只有一個會勝出,并成功更新,其余均會失敗。失敗的線程不會被掛起,僅是被告知失敗,并且允許再次嘗試,當(dāng)然也允許失敗的線程放棄操作?;谶@樣的原理,
CAS 操作即使沒有鎖,也可以發(fā)現(xiàn)其他線程對當(dāng)前線程的干擾,并進行恰當(dāng)?shù)奶幚怼?
原子包 java.util.concurrent.atomic(鎖自旋)
JDK1.5 的原子包:java.util.concurrent.atomic 這個包里面提供了一組原子類。其基本的特性就是在多線程環(huán)境下,當(dāng)有多個線程同時執(zhí)行這些類的實例包含的方法時,具有排他性,即當(dāng)某個線程進入方法,執(zhí)行其中的指令時,不會被其他線程打斷,而別的線程就像自旋鎖一樣,一直等
到該方法執(zhí)行完成,才由 JVM 從等待隊列中選擇一個另一個線程進入,這只是一種邏輯上的理解。
相對于對于 synchronized 這種阻塞算法,CAS 是非阻塞算法的一種常見實現(xiàn)。由于一般 CPU 切換時間比 CPU 指令集操作更加長, 所以 J.U.C 在性能上有了很大的提升。如下代碼:
public class AtomicInteger extends Number implements java.io.Serializable { ?
? ? ? ?private volatile int value; ?
? ? ? ?public final int get() { ? ? ? ? ?
? ? ? ?return value; ?
? ? } ?
? ? public final int getAndIncrement() { ?
? ? ? ?for (;;) { ?
? ? ? ?//CAS 自旋,一直嘗試,直達成功
? ? ? ? ? int current = get(); ? ? ? ? ?
? ? ? ? ? int next = current + 1; ? ? ? ? ?
? ? ? ? ? if (compareAndSet(current, next)) ? ? ? ? ? ? ? ? ? return current; ?
? ? ? ?} ?
? ?} ?
? ?public final boolean compareAndSet(int expect, int update) { ? ? ? ?
? ?return unsafe.compareAndSwapInt(this, valueOffset, expect, update); ?
? ?} ?
}
getAndIncrement 采用了 CAS 操作,每次從內(nèi)存中讀取數(shù)據(jù)然后將此數(shù)據(jù)和+1 后的結(jié)果進行 CAS 操作,如果成功就返回結(jié)果,否則重試直到成功為止。而 compareAndSet 利用 JNI 來完成
CPU 指令的操作。

ABA 問題
CAS 會導(dǎo)致“ABA 問題”。CAS 算法實現(xiàn)一個重要前提需要取出內(nèi)存中某時刻的數(shù)據(jù),而在下時刻比較并替換,那么在這個時間差類會導(dǎo)致數(shù)據(jù)的變化。
比如說一個線程 one 從內(nèi)存位置 V 中取出 A,這時候另一個線程 two 也從內(nèi)存中取出 A,并且 two 進行了一些操作變成了 B,然后 two 又將 V 位置的數(shù)據(jù)變成 A,這時候線程 one 進行 CAS 操作發(fā)現(xiàn)內(nèi)存中仍然是 A,然后 one 操作成功。盡管線程 one 的 CAS 操作成功,但是不代表這個過程就是沒有問題的。
部分樂觀鎖的實現(xiàn)是通過版本號(version)的方式來解決 ABA 問題,樂觀鎖每次在執(zhí)行數(shù)據(jù)的修改操作時,都會帶上一個版本號,一旦版本號和數(shù)據(jù)的版本號一致就可以執(zhí)行修改操作并對版本號執(zhí)行+1 操作,否則就執(zhí)行失敗。因為每次操作的版本號都會隨之增加,所以不會出現(xiàn) ABA 問題,因為版本號只會增加不會減少。