Java 多線程 - CAS

前言

記錄在學習線程安全知識點中,關於CAS的有關知識點。

線程安全是指:多個線程不管以何種方式訪問某個類,並且在主調代碼中不需要進行同步,都能表現正確的行爲。

常見的線程安全實現方法分爲不可變對象、線程互斥同步、非阻塞同步、線程本地存儲等方案,本文要講的就是非阻塞同步中的核心CAS.

非阻塞同步

從處理問題的方式上說,互斥同步屬於一種悲觀的併發策略。

隨着硬件指令集的發展,我們可以採用**基於衝突檢查的樂觀併發策略,通俗地說,就是先行操作,如果沒有其他線程爭用共享數據,那操作就成功了;**如果共享數據有爭用,產生了衝突,那就再採取其他的補償措施(最常見的補償措施就是不斷地重試,直到成功爲止),這種樂觀的併發策略的許多實現偶讀不需要把線程掛起,因此這種同步操作稱爲非阻塞同步。

CAS

樂觀鎖需要操作和衝突檢測這兩個步驟具備原子性,這裏就不能再使用互斥同步來保證了,只能靠硬件來完成硬件支持的原子性操作最典型的是:比較並交換(Compare-and-Swap,CAS)CAS 指令需要有 3 個操作數,分別是內存地址 V、舊的預期值 A 和新值 B。當執行操作時,只有當 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內的valueexpect相等,就證明沒有其他線程改變過這個變量,那麼就更新它爲update,如果這一步的CAS沒有成功,那就採用自旋的方式繼續進行CAS操作,取出乍一看這也是兩個步驟了啊,其實在JNI裏是藉助於一個CPU指令完成的。所以還是原子操作。

CAS 的問題

  • ABA問題
    • 描述:如果一個變量初次讀取的時候是 A 值,它的值被改成了 B,後來又被改回爲 A,那 CAS 操作就會誤認爲它從來沒有被改變過。
    • 解決方案:J.U.C 包提供了一個帶有標記的原子引用類 AtomicStampedReference 來解決這個問題,它可以通過控制變量值的版本來保證 CAS 的正確性。大部分情況下 ABA 問題不會影響程序併發的正確性,如果真的需要解決 ABA 問題,改用傳統的互斥同步可能會比原子類更高效
  • 循環時間長開銷大
    • 自旋CAS(也就是不成功就一直循環執行直到成功)如果長時間不成功,會給CPU帶來比較大的執行開銷。
  • 只能保證一個共享變量的原子操作
    • CAS 只對單個共享變量有效,當操作涉及跨多個共享變量時CAS 無效。但是從 JDK 1.5開始,提供了AtomicReference類來保證引用對象之間的原子性,你可以把多個變量放在一個對象裏來進行 CAS操作.所以我們可以使用鎖或者利用AtomicReference類把多個共享變量合併成一個共享變量來操作。

CAS與synchronized的使用情景

  • 簡單的來說CAS適用於寫比較少的情況下(多讀場景,衝突一般較少)
  • synchronized適用於寫比較多的情況下(多寫場景,衝突一般較多)
  • 對於資源競爭較少(線程衝突較輕)的情況,使用synchronized同步鎖進行線程阻塞和喚醒切換以及用戶態內核態間的切換操作額外浪費消耗cpu資源;而CAS基於硬件實現,不需要進入內核,不需要切換線程,操作自旋機率較少,因此可以獲得更高的性能。
  • 對於資源競爭嚴重(線程衝突嚴重)的情況,CAS自旋的概率會比較大,從而浪費更多的CPU資源,效率低於synchronized。

CAS 的應用

使用 CAS 原子指令來處理對數據的併發訪問,這是非阻塞算法得以實現的基礎。關於非阻塞算法是屬於J.U.C中併發容器部分的知識,屬於比較難的內容。目前先引用幾篇文章。作爲記錄,之後有機會再詳細學習。

  1. 非阻塞算法在併發容器中的實現
  2. 非阻塞同步算法實戰(一)
  3. 非阻塞同步算法實戰(二)-BoundlessCyclicBarrier
  4. 非阻塞同步算法實戰(三)-LatestResultsProvider

參考

  1. 《深入理解Java虛擬機》
  2. https://www.ibm.com/developerworks/cn/java/j-lo-concurrent/
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章