原子操作出現的先決條件

讓我們看一下這個示例,以幫助您了解原子操作的工作原理:

public class Counter {
    int count;

    public void increment() {
        count++;
    }
}

當我們有一個線程時,一切都很好,但是如果我們添加多線程,我們會得到錯誤的結果,這都是因為增量操作不是一個操作,而是三個:獲取當前值的請求數數,然後將其遞增 1 並再次寫入數數.

當兩個線程想要增加一個變量時,您很可能會丟失數據。也就是說,兩個線程都收到 100,因此,兩者都將寫入 101 而不是預期值 102。

以及如何解決?你需要使用鎖。synchronized關鍵字有助於解決這個問題,使用它可以保證一個線程一次訪問該方法。

public class SynchronizedCounterWithLock {
    private volatile int count;

    public synchronized void increment() {
        count++;
    }
}

另外,您需要添加volatile關鍵字,以確保線程間引用的正確可見性。我們在上面回顧了他的工作。

但仍有缺點。最大的一個是性能,在那個時間點,當許多線程試圖獲取鎖並且一個獲得寫機會時,其餘線程將被阻塞或掛起,直到線程被釋放。

所有這些進程、阻塞、切換到另一個狀態對於系統性能來說都是非常昂貴的。

原子操作

該算法使用低級機器指令,例如比較和交換(CAS,compare-and-swap,確保數據完整性並且已經有大量研究)。

典型的 CAS 操作對三個操作數進行操作:

  • 工作內存空間(M)
  • 變量的現有預期值 (A)
  • 要設置的新值 (B)

CAS 以原子方式將 M 更新為 B,但前提是 M 的值與 A 相同,否則不採取任何操作。

在第一種和第二種情況下,都會返回M的值,這樣可以將獲取值、比較值、更新值三個步驟結合起來。這一切都變成了機器級別的一次操作。

當多線程應用程序訪問變量並嘗試更新它並應用 CAS 時,其中一個線程將獲取它並能夠更新它。但與鎖不同的是,其他線程只會收到無法更新值的錯誤。然後他們將繼續進行進一步的工作,而這種工作完全排除了轉換。

在這種情況下,邏輯變得更加困難,因為我們必須處理 CAS 操作未成功運行的情況。我們將只對代碼建模,以便在操作成功之前它不會繼續。

原子類型簡介

您是否遇到過需要為最簡單的int類型變量設置同步的情況?

我們已經介紹過的第一種方法是使用volatile + synchronized。但也有特殊的 Atomic* 類。

如果我們使用 CAS,那麼與第一種方法相比,操作工作得更快。此外,我們還有特殊且非常方便的方法來添加值以及遞增和遞減操作。

AtomicBoolean AtomicInteger AtomicLong AtomicIntegerArray AtomicLongArray是其中操作是原子的類。下面我們將與他們一起分析工作。

原子整數

AtomicInteger類除了提供擴展的原子操作外,還提供對可以原子方式讀取和寫入的int值的操作。

它具有getset方法,其工作方式類似於讀取和寫入變量。

也就是說,“先於發生”與我們之前討論過的相同變量的任何後續接收。原子compareAndSet方法也具有這些內存一致性功能。

所有返回新值的操作都是原子執行的:

int addAndGet (int 增量) 將特定值添加到當前值。
布爾比較和設置(預期 int,更新 int) 如果當前值與預期值匹配,則將值設置為給定的更新值。
int decrementAndGet() 將當前值減一。
int getAndAdd(int 增量) 將給定值添加到當前值。
int getAndDecrement() 將當前值減一。
int getAndIncrement() 將當前值增加一。
int getAndSet(int newValue) 設置給定值並返回舊值。
int incrementAndGet() 將當前值增加一。
惰性集(int newValue) 最後設置為給定值。
布爾 weakCompareAndSet(預期,更新 int) 如果當前值與預期值匹配,則將值設置為給定的更新值。

例子:

ExecutorService executor = Executors.newFixedThreadPool(5);
IntStream.range(0, 50).forEach(i -> executor.submit(atomicInteger::incrementAndGet));
executor.shutdown();
executor.awaitTermination(Long.MAX_VALUE, TimeUnit.HOURS);

System.out.println(atomicInteger.get()); // prints 50