Java 多線程 - CAS

前言

記錄在學習線程安全知識點中,關于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 操作。

image

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

image

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

image

compareAndSwapInt(var1, var2, var5, var5 + var4 其實換成compareAndSwapInt(obj, offset, expect, update)比較清楚,意思就是如果obj內的valueexpect相等,就證明沒有其他線程改變過這個變量,那么就更新它為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ā)容器部分的知識,屬于比較難的內容。目前先引用幾篇文章。作為記錄,之后有機會再詳細學習。

  1. 非阻塞算法在并發(fā)容器中的實現

  2. 非阻塞同步算法實戰(zhàn)(一)

  3. 非阻塞同步算法實戰(zhàn)(二)-BoundlessCyclicBarrier

  4. 非阻塞同步算法實戰(zhàn)(三)-LatestResultsProvider

參考

  1. 《深入理解Java虛擬機》
  2. https://www.ibm.com/developerworks/cn/java/j-lo-concurrent/
?著作權歸作者所有,轉載或內容合作請聯系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容