Java中的13個原子操作類介紹

十步殺一人_千里不留行發表於2020-02-01

一、引言

在 JDK1.5 中新增 java.util.concurrent(J.U.C) 包,它建立在 CAS 之上。CAS 是非阻塞演算法的一種常見實現,相對於 synchronized 這種阻塞演算法,它的效能更好。

DK1.5 中引入了底層的支援,在 int、long 和物件的引用等型別上都公開了 CAS 的操作,並且 JVM 把它們編譯為底層硬體提供的最有效的方法,在執行 CAS 的平臺上,執行時把它們編譯為相應的機器指令。在 java.util.concurrent.atomic 包下面的所有的原子變數型別中,比如 AtomicInteger,都使用了這些底層的JVM支援為數字型別的引用型別提供一種高效的 CAS 操作。

二、Java原子類介紹

​atomic 包中的 13 個類,屬於 4 種型別的原子更新方式.

  • (1)原子更新基本型別

  • (2)原子更新陣列

  • (3)原子更新引用

  • (4)原子更新屬性

atomic 包裡的類基本都是使用 Unsafe 實現的包裝類.

1.原子更新基本型別

  • AtomicBoolean:原子更新布林型別。
  •  AtomicInteger:原子更新整型。
  •  AtomicLong:原子更新長整型。

本類以 AtomicInteger 進行講解:

AtomicInteger 是一個支援原子操作的 Integer 類,就是保證對 AtomicInteger 型別變數的增加和減少操作是原子性的,不會出現多個執行緒下的資料不一致問題。如果不使用 AtomicInteger,要實現一個按順序獲取的 ID,就必須在每次獲取時進行加鎖操作,以避免出現併發時獲取到同樣的 ID 的現象。

  • int addAndGet(int delta) 以原子方式將輸入的數值與例項中的值相加,並返回結果;
  • boolean compareAndSet(int expect, int update) 如果輸入的數值等於預期值,則以原子的方式將該值設定為輸入的值;
  • int getAndIncrement() 以原子方式將當前值加 1,注意,這裡返回的是自增前的值;
  • void lazySet(int newValue) 最終會設定成 newValue,使用 lazySet 設定值後,可能導致其他執行緒在之後的一小段時間內還是可以讀到 舊值;
  • int getAndSet(int newValue) 以原子方式設定為 newValue 的值,並返回舊值。

那麼getAndIncrement 是如何實現原子操作的呢?

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

我們可以看到,它的實現原理是死迴圈 + CAS。

2.原子更新陣列

通過原子的方式更新陣列裡的某個元素,Atomic 包提供了以下 3 個類。

  • AtomicIntegerArray:原子更新整型陣列裡的元素。
  • AtomicLongArray:原子更新長整型陣列裡的元素。
  • AtomicReferenceArray:原子更新引用型別陣列裡的元素。

AtomicIntegerArray 類提供方法如下:

  • int addAndGet(int i, int delta) 以原子方式將輸入值與陣列中的索引 i 的元素相加 
  • boolean compareAndSet(int i, int expect, int update) 如果當前值等於預期值,則以原子方式將陣列位置 i 的元素設定成 update 值.

3.原子更新引用型別

原子更新基本型別的 AtomicInteger,只能更新一個變數,如果要原子更新多個變數,就需要使用這個原子更新引用型別提供的類。Atomic 包提供了以下 3 個類。

  • AtomicReference:原子更新引用型別。
  • AtomicReferenceFieldUpdater:原子更新引用型別裡的欄位。
  • AtomicMarkableReference:原子更新帶有標記位的引用型別。

4.原子更新屬性

如果需原子地更新某個類裡的某個欄位時,就需要使用原子更新欄位類,Atomic 包提供了以下 3 個類進行原子欄位更新。

  • AtomicIntegerFieldUpdater:原子更新整型的欄位的更新器。
  • AtomicLongFieldUpdater:原子更新長整型欄位的更新器。
  • AtomicStampedReference:原子更新帶有版本號的引用型別。該類將整數值與引用關聯起來,可用於原子的更新資料和資料的版本號,可以解決使用 CAS 進行原子更新時可能出現的 ABA 問題。

三、原子類的應用場景

原子變數不需要使用鎖或其他同步機制來保護對其值的併發訪問。所有操作都是基於CAS原子操作的。他保證了多執行緒在同一時間操作一個原子變數而不會產生資料不一致的錯誤,並且他的效能優於使用同步機制保護的普通變數,譬如說在多執行緒環境中統計次數就可以使用原子變數。具體場景總結如下:

  1. 在多執行緒環境中,可以很多情況可使用該關鍵字替換synchronized;
  2. 限制併發流量:在高併發的場景下,也可以用來限制介面的流量,超過併發的數量的閾值進行熔斷等操作;
  3. AtomicBoolean解決了初始化中多執行緒環境下的併發安全問題。

四、總結

  1. 藉助volatile原語,保證執行緒間的資料是可見的(共享的)。
  2. 2、採用了CAS操作,每次從記憶體中讀取資料然後將此資料和+1後的結果進行CAS操作,如果成功就返回結果,否則重試直到成 功為止 compareAndSet利用JNI來完成CPU指令的操作。
  3. ABA問題:比如說執行緒one從記憶體位置V中取出A,這時候另一個執行緒two也從記憶體中取出A,並且two進行了一些操作變成了B,然後two又將V位置的資料變成A,這時候執行緒one進行CAS操作發現記憶體中仍然是A,然後one操作成功。儘管執行緒one的CAS操作成功,但是不代表這個過程就是沒有問題的。如果連結串列的頭在變化了兩次後恢復了原值,但是不代表連結串列就沒有變化。要解決"ABA問題",我們需要增加一個版本號,在更新變數值的時候不應該只更新一個變數值,而應該更新兩個值,分別是變數值和版本號
  4. synchronized的成本相對較高,需要獲取鎖物件,釋放鎖物件  使用原子變數可以避免多執行緒的優先順序倒置和死鎖情況的發生,提升在高併發處理下的效能。

我的微信公眾號:架構真經(id:gentoo666),分享Java乾貨,高併發程式設計,熱門技術教程,微服務及分散式技術,架構設計,區塊鏈技術,人工智慧,大資料,Java面試題,以及前沿熱門資訊等。每日更新哦!

相關文章