前言
記錄在學習線程安全知識點中,關于CAS的有關知識點。
線程安全是指:多個線程不管以何種方式訪問某個類,并且在主調代碼中不需要進行同步,都能表現正確的行為。
常見的線程安全實現方法分為不可變對象、線程互斥同步、非阻塞同步、線程本地存儲等方案,本文要講的就是非阻塞同步中的核心CAS.
非阻塞同步
從處理問題的方式上說,互斥同步屬于一種悲觀的并發(fā)策略。
隨著硬件指令集的發(fā)展,我們可以采用基于沖突檢查的樂觀并發(fā)策略,通俗地說,就是先行操作,如果沒有其他線程爭用共享數據,那操作就成功了;如果共享數據有爭用,產生了沖突,那就再采取其他的補償措施(最常見的補償措施就是不斷地重試,直到成功為止),這種樂觀的并發(fā)策略的許多實現偶讀不需要把線程掛起,因此這種同步操作稱為非阻塞同步。
CAS
樂觀鎖需要操作和沖突檢測這兩個步驟具備原子性,這里就不能再使用互斥同步來保證了,只能靠硬件來完成。硬件支持的原子性操作最典型的是:比較并交換(Compare-and-Swap,CAS)。CAS 指令需要有 3 個操作數,分別是內存地址 V、舊的預期值 A 和新值 B。當執(zhí)行操作時,只有當 V 的值等于 A,才將 V 的值更新為 B。
各種Atomic開頭的原子類,內部都應用到了CAS。就拿AtomicInteger為例。
J.U.C 包里面的原子類 AtomicInteger 的方法調用了 Unsafe 類的 CAS 操作。

看看AtomicInteger對象一次自增,CAS起了什么作用,以下代碼是 incrementAndGet() 的源碼,可以看到內部調用了 Unsafe 對象的 getAndAddInt() 。

以下代碼是 getAndAddInt()源碼,var1 指示對象內存地址,var2指示該字段相對對象內存地址的偏移,var4 指示操作需要加的數值,這里為 1。通過 getIntVolatile(var1, var2) 得到舊的預期值,通過調用 compareAndSwapInt() 來進行 CAS比較,如果該字段內存地址中的值等于var5,那么就更新內存地址為 var1+var2 的變量為 var5+var4。

compareAndSwapInt(var1, var2, var5, var5 + var4 其實換成compareAndSwapInt(obj, offset, expect, update)比較清楚,意思就是如果obj內的value和expect相等,就證明沒有其他線程改變過這個變量,那么就更新它為update,如果這一步的CAS沒有成功,那就采用自旋的方式繼續(xù)進行CAS操作,取出乍一看這也是兩個步驟了啊,其實在JNI里是借助于一個CPU指令完成的。所以還是原子操作。
CAS 的問題
-
ABA問題
- 描述:如果一個變量初次讀取的時候是 A 值,它的值被改成了 B,后來又被改回為 A,那 CAS 操作就會誤認為它從來沒有被改變過。
- 解決方案:J.U.C 包提供了一個帶有標記的原子引用類 AtomicStampedReference 來解決這個問題,它可以通過控制變量值的版本來保證 CAS 的正確性。大部分情況下 ABA 問題不會影響程序并發(fā)的正確性,如果真的需要解決 ABA 問題,改用傳統(tǒng)的互斥同步可能會比原子類更高效。
- 循環(huán)時間長開銷大
- 自旋
CAS(也就是不成功就一直循環(huán)執(zhí)行直到成功)如果長時間不成功,會給CPU帶來比較大的執(zhí)行開銷。
- 自旋
- 只能保證一個共享變量的原子操作
-
CAS只對單個共享變量有效,當操作涉及跨多個共享變量時CAS無效。但是從 JDK 1.5開始,提供了AtomicReference類來保證引用對象之間的原子性,你可以把多個變量放在一個對象里來進行CAS操作.所以我們可以使用鎖或者利用AtomicReference類把多個共享變量合并成一個共享變量來操作。
-
CAS與synchronized的使用情景
- 簡單的來說CAS適用于寫比較少的情況下(多讀場景,沖突一般較少)
- synchronized適用于寫比較多的情況下(多寫場景,沖突一般較多)
- 對于資源競爭較少(線程沖突較輕)的情況,使用synchronized同步鎖進行線程阻塞和喚醒切換以及用戶態(tài)內核態(tài)間的切換操作額外浪費消耗cpu資源;而CAS基于硬件實現,不需要進入內核,不需要切換線程,操作自旋幾率較少,因此可以獲得更高的性能。
- 對于資源競爭嚴重(線程沖突嚴重)的情況,CAS自旋的概率會比較大,從而浪費更多的CPU資源,效率低于synchronized。
CAS 的應用
使用 CAS 原子指令來處理對數據的并發(fā)訪問,這是非阻塞算法得以實現的基礎。關于非阻塞算法是屬于J.U.C中并發(fā)容器部分的知識,屬于比較難的內容。目前先引用幾篇文章。作為記錄,之后有機會再詳細學習。