Atomic 原子類總結(轉)
原文:https://javaguide.cn/java/concurrent/atomic-classes.html
作者:Guide
Atomic 原子類介紹
Atomic 翻譯成中文是“原子”的意思。在化學上,原子是構成物質的最小單位,在化學反應中不可分割。在編程中,Atomic 指的是一個操作具有原子性,即該操作不可分割、不可中斷。即使在多個線程同時執行時,該操作要么全部執行完成,要么不執行,不會被其他線程看到部分完成的狀態。
原子類簡單來說就是具有原子性操作特征的類。
java.util.concurrent.atomic 包中的 Atomic 原子類提供了一種線程安全的方式來操作單個變量。
Atomic 類依賴于 CAS(Compare-And-Swap,比較并交換)樂觀鎖來保證其方法的原子性,而不需要使用傳統的鎖機制(如 synchronized 塊或 ReentrantLock)。
這篇文章我們只介紹 Atomic 原子類的概念,具體實現原理可以閱讀筆者寫的這篇文章:CAS 詳解。

根據操作的數據類型,可以將 JUC 包中的原子類分為 4 類:
1、基本類型
使用原子的方式更新基本類型
AtomicInteger:整型原子類AtomicLong:長整型原子類AtomicBoolean:布爾型原子類
2、數組類型
使用原子的方式更新數組里的某個元素
AtomicIntegerArray:整型數組原子類AtomicLongArray:長整型數組原子類AtomicReferenceArray:引用類型數組原子類
3、引用類型
AtomicReference:引用類型原子類AtomicMarkableReference:原子更新帶有標記的引用類型。該類將 boolean 標記與引用關聯起來,AtomicMarkableReference不能解決 ABA 問題,(參見:issue#626)。AtomicStampedReference:原子更新帶有版本號的引用類型。該類將整數值與引用關聯起來,可用于解決原子的更新數據和數據的版本號,可以解決使用 CAS 進行原子更新時可能出現的 ABA 問題。
4、對象的屬性修改類型
AtomicIntegerFieldUpdater:原子更新整型字段的更新器AtomicLongFieldUpdater:原子更新長整型字段的更新器AtomicReferenceFieldUpdater:原子更新引用類型里的字段
基本類型原子類
使用原子的方式更新基本類型
AtomicInteger:整型原子類AtomicLong:長整型原子類AtomicBoolean:布爾型原子類
上面三個類提供的方法幾乎相同,所以我們這里以 AtomicInteger 為例子來介紹。
AtomicInteger 類常用方法 :
public final int get() //獲取當前的值 public final int getAndSet(int newValue)//獲取當前的值,并設置新的值 public final int getAndIncrement()//獲取當前的值,并自增 public final int getAndDecrement() //獲取當前的值,并自減 public final int getAndAdd(int delta) //獲取當前的值,并加上預期的值 boolean compareAndSet(int expect, int update) //如果輸入的數值等于預期值,則以原子方式將該值設置為輸入值(update) public final void lazySet(int newValue)//最終設置為newValue, lazySet 提供了一種比 set 方法更弱的語義,可能導致其他線程在之后的一小段時間內還是可以讀到舊的值,但可能更高效。
AtomicInteger 類使用示例 :
// 初始化 AtomicInteger 對象,初始值為 0 AtomicInteger atomicInt = new AtomicInteger(0); // 使用 getAndSet 方法獲取當前值,并設置新值為 3 int tempValue = atomicInt.getAndSet(3); System.out.println("tempValue: " + tempValue + "; atomicInt: " + atomicInt); // 使用 getAndIncrement 方法獲取當前值,并自增 1 tempValue = atomicInt.getAndIncrement(); System.out.println("tempValue: " + tempValue + "; atomicInt: " + atomicInt); // 使用 getAndAdd 方法獲取當前值,并增加指定值 5 tempValue = atomicInt.getAndAdd(5); System.out.println("tempValue: " + tempValue + "; atomicInt: " + atomicInt); // 使用 compareAndSet 方法進行原子性條件更新,期望值為 9,更新值為 10 boolean updateSuccess = atomicInt.compareAndSet(9, 10); System.out.println("Update Success: " + updateSuccess + "; atomicInt: " + atomicInt); // 獲取當前值 int currentValue = atomicInt.get(); System.out.println("Current value: " + currentValue); // 使用 lazySet 方法設置新值為 15 atomicInt.lazySet(15); System.out.println("After lazySet, atomicInt: " + atomicInt);
輸出:
tempValue: 0; atomicInt: 3 tempValue: 3; atomicInt: 4 tempValue: 4; atomicInt: 9 Update Success: true; atomicInt: 10 Current value: 10 After lazySet, atomicInt: 15
數組類型原子類
使用原子的方式更新數組里的某個元素
AtomicIntegerArray:整形數組原子類AtomicLongArray:長整形數組原子類AtomicReferenceArray:引用類型數組原子類
上面三個類提供的方法幾乎相同,所以我們這里以 AtomicIntegerArray 為例子來介紹。
AtomicIntegerArray 類常用方法:
public final int get(int i) //獲取 index=i 位置元素的值 public final int getAndSet(int i, int newValue)//返回 index=i 位置的當前的值,并將其設置為新值:newValue public final int getAndIncrement(int i)//獲取 index=i 位置元素的值,并讓該位置的元素自增 public final int getAndDecrement(int i) //獲取 index=i 位置元素的值,并讓該位置的元素自減 public final int getAndAdd(int i, int delta) //獲取 index=i 位置元素的值,并加上預期的值 boolean compareAndSet(int i, int expect, int update) //如果輸入的數值等于預期值,則以原子方式將 index=i 位置的元素值設置為輸入值(update) public final void lazySet(int i, int newValue)//最終 將index=i 位置的元素設置為newValue,使用 lazySet 設置之后可能導致其他線程在之后的一小段時間內還是可以讀到舊的值。
AtomicIntegerArray 類使用示例 :
int[] nums = {1, 2, 3, 4, 5, 6}; // 創建 AtomicIntegerArray AtomicIntegerArray atomicArray = new AtomicIntegerArray(nums); // 打印 AtomicIntegerArray 中的初始值 System.out.println("Initial values in AtomicIntegerArray:"); for (int j = 0; j < nums.length; j++) { System.out.print("Index " + j + ": " + atomicArray.get(j) + " "); } // 使用 getAndSet 方法將索引 0 處的值設置為 2,并返回舊值 int tempValue = atomicArray.getAndSet(0, 2); System.out.println("\nAfter getAndSet(0, 2):"); System.out.println("Returned value: " + tempValue); for (int j = 0; j < atomicArray.length(); j++) { System.out.print("Index " + j + ": " + atomicArray.get(j) + " "); } // 使用 getAndIncrement 方法將索引 0 處的值加 1,并返回舊值 tempValue = atomicArray.getAndIncrement(0); System.out.println("\nAfter getAndIncrement(0):"); System.out.println("Returned value: " + tempValue); for (int j = 0; j < atomicArray.length(); j++) { System.out.print("Index " + j + ": " + atomicArray.get(j) + " "); } // 使用 getAndAdd 方法將索引 0 處的值增加 5,并返回舊值 tempValue = atomicArray.getAndAdd(0, 5); System.out.println("\nAfter getAndAdd(0, 5):"); System.out.println("Returned value: " + tempValue); for (int j = 0; j < atomicArray.length(); j++) { System.out.print("Index " + j + ": " + atomicArray.get(j) + " "); }
輸出:
Initial values in AtomicIntegerArray: Index 0: 1 Index 1: 2 Index 2: 3 Index 3: 4 Index 4: 5 Index 5: 6 After getAndSet(0, 2): Returned value: 1 Index 0: 2 Index 1: 2 Index 2: 3 Index 3: 4 Index 4: 5 Index 5: 6 After getAndIncrement(0): Returned value: 2 Index 0: 3 Index 1: 2 Index 2: 3 Index 3: 4 Index 4: 5 Index 5: 6 After getAndAdd(0, 5): Returned value: 3 Index 0: 8 Index 1: 2 Index 2: 3 Index 3: 4 Index 4: 5 Index 5: 6
引用類型原子類
基本類型原子類只能更新一個變量,如果需要原子更新多個變量,需要使用 引用類型原子類。
AtomicReference:引用類型原子類AtomicStampedReference:原子更新帶有版本號的引用類型。該類將整數值與引用關聯起來,可用于解決原子的更新數據和數據的版本號,可以解決使用 CAS 進行原子更新時可能出現的 ABA 問題。AtomicMarkableReference:原子更新帶有標記的引用類型。該類將 boolean 標記與引用關聯起來,AtomicMarkableReference不能解決 ABA 問題
上面三個類提供的方法幾乎相同,所以我們這里以 AtomicReference 為例子來介紹。
AtomicReference 類使用示例 :
// Person 類 class Person { private String name; private int age; //省略getter/setter和toString } // 創建 AtomicReference 對象并設置初始值 AtomicReference<Person> ar = new AtomicReference<>(new Person("SnailClimb", 22)); // 打印初始值 System.out.println("Initial Person: " + ar.get().toString()); // 更新值 Person updatePerson = new Person("Daisy", 20); ar.compareAndSet(ar.get(), updatePerson); // 打印更新后的值 System.out.println("Updated Person: " + ar.get().toString()); // 嘗試再次更新 Person anotherUpdatePerson = new Person("John", 30); boolean isUpdated = ar.compareAndSet(updatePerson, anotherUpdatePerson); // 打印是否更新成功及最終值 System.out.println("Second Update Success: " + isUpdated); System.out.println("Final Person: " + ar.get().toString());
輸出:
Initial Person: Person{name='SnailClimb', age=22}
Updated Person: Person{name='Daisy', age=20}
Second Update Success: true
Final Person: Person{name='John', age=30}
AtomicStampedReference 類使用示例 :
// 創建一個 AtomicStampedReference 對象,初始值為 "SnailClimb",初始版本號為 1 AtomicStampedReference<String> asr = new AtomicStampedReference<>("SnailClimb", 1); // 打印初始值和版本號 int[] initialStamp = new int[1]; String initialRef = asr.get(initialStamp); System.out.println("Initial Reference: " + initialRef + ", Initial Stamp: " + initialStamp[0]); // 更新值和版本號 int oldStamp = initialStamp[0]; String oldRef = initialRef; String newRef = "Daisy"; int newStamp = oldStamp + 1; boolean isUpdated = asr.compareAndSet(oldRef, newRef, oldStamp, newStamp); System.out.println("Update Success: " + isUpdated); // 打印更新后的值和版本號 int[] updatedStamp = new int[1]; String updatedRef = asr.get(updatedStamp); System.out.println("Updated Reference: " + updatedRef + ", Updated Stamp: " + updatedStamp[0]); // 嘗試用錯誤的版本號更新 boolean isUpdatedWithWrongStamp = asr.compareAndSet(newRef, "John", oldStamp, newStamp + 1); System.out.println("Update with Wrong Stamp Success: " + isUpdatedWithWrongStamp); // 打印最終的值和版本號 int[] finalStamp = new int[1]; String finalRef = asr.get(finalStamp); System.out.println("Final Reference: " + finalRef + ", Final Stamp: " + finalStamp[0]);
輸出結果如下:
Initial Reference: SnailClimb, Initial Stamp: 1 Update Success: true Updated Reference: Daisy, Updated Stamp: 2 Update with Wrong Stamp Success: false Final Reference: Daisy, Final Stamp: 2
AtomicMarkableReference 類使用示例 :
// 創建一個 AtomicMarkableReference 對象,初始值為 "SnailClimb",初始標記為 false AtomicMarkableReference<String> amr = new AtomicMarkableReference<>("SnailClimb", false); // 打印初始值和標記 boolean[] initialMark = new boolean[1]; String initialRef = amr.get(initialMark); System.out.println("Initial Reference: " + initialRef + ", Initial Mark: " + initialMark[0]); // 更新值和標記 String oldRef = initialRef; String newRef = "Daisy"; boolean oldMark = initialMark[0]; boolean newMark = true; boolean isUpdated = amr.compareAndSet(oldRef, newRef, oldMark, newMark); System.out.println("Update Success: " + isUpdated); // 打印更新后的值和標記 boolean[] updatedMark = new boolean[1]; String updatedRef = amr.get(updatedMark); System.out.println("Updated Reference: " + updatedRef + ", Updated Mark: " + updatedMark[0]); // 嘗試用錯誤的標記更新 boolean isUpdatedWithWrongMark = amr.compareAndSet(newRef, "John", oldMark, !newMark); System.out.println("Update with Wrong Mark Success: " + isUpdatedWithWrongMark); // 打印最終的值和標記 boolean[] finalMark = new boolean[1]; String finalRef = amr.get(finalMark); System.out.println("Final Reference: " + finalRef + ", Final Mark: " + finalMark[0]);
輸出結果如下:
Initial Reference: SnailClimb, Initial Mark: false Update Success: true Updated Reference: Daisy, Updated Mark: true Update with Wrong Mark Success: false Final Reference: Daisy, Final Mark: true
對象的屬性修改類型原子類
如果需要原子更新某個類里的某個字段時,需要用到對象的屬性修改類型原子類。
AtomicIntegerFieldUpdater:原子更新整形字段的更新器AtomicLongFieldUpdater:原子更新長整形字段的更新器AtomicReferenceFieldUpdater:原子更新引用類型里的字段的更新器
以AtomicIntegerFieldUpdater為例,原子地更新對象的屬性需要兩步。第一步,因為對象的屬性修改類型原子類都是抽象類,所以每次使用都必須使用靜態方法 newUpdater()創建一個更新器,并且需要設置想要更新的類和屬性。第二步,更新的對象屬性必須使用 volatile int 修飾符。
上面三個類提供的方法幾乎相同,所以我們這里以 AtomicIntegerFieldUpdater為例子來介紹。
AtomicIntegerFieldUpdater 類使用示例 :
// Person 類 class Person { private String name; // 要使用 AtomicIntegerFieldUpdater,字段必須是 volatile int volatile int age; //省略getter/setter和toString } // 創建 AtomicIntegerFieldUpdater 對象 AtomicIntegerFieldUpdater<Person> ageUpdater = AtomicIntegerFieldUpdater.newUpdater(Person.class, "age"); // 創建 Person 對象 Person person = new Person("SnailClimb", 22); // 打印初始值 System.out.println("Initial Person: " + person); // 更新 age 字段 ageUpdater.incrementAndGet(person); // 自增 System.out.println("After Increment: " + person); ageUpdater.addAndGet(person, 5); // 增加 5 System.out.println("After Adding 5: " + person); ageUpdater.compareAndSet(person, 28, 30); // 如果當前值是 28,則設置為 30 System.out.println("After Compare and Set (28 to 30): " + person); // 嘗試使用錯誤的比較值進行更新 boolean isUpdated = ageUpdater.compareAndSet(person, 28, 35); // 這次應該失敗 System.out.println("Compare and Set (28 to 35) Success: " + isUpdated); System.out.println("Final Person: " + person);
輸出結果:
Initial Person: Name: SnailClimb, Age: 22 After Increment: Name: SnailClimb, Age: 23 After Adding 5: Name: SnailClimb, Age: 28 After Compare and Set (28 to 30): Name: SnailClimb, Age: 30 Compare and Set (28 to 35) Success: false Final Person: Name: SnailClimb, Age: 30
參考
- 《Java 并發編程的藝術》

浙公網安備 33010602011771號