當大型系統開發在一開始設計時,針對一些變數沒有考量到 Thread Safe 問題,在後面改造時需要耗費大量經歷重新把這些變數改成 AtomicInteger 等類別。但是這樣一來不僅需要耗工,還破壞了程式開發的開閉原則。所謂開閉原則就是系統對功能新增是開放態度,對功能修改是保守態度。不輕易改 code,但是可以新增功能。
AtomicFieldUpdater 可以解決這種尷尬的問題,他可以在幾乎不改動原有 code 基礎上,讓普通變數也享受 CAS 操作帶來的好處。
AtomicFieldUpdater 有三種:
-
AtomicIntegerFieldUpdater
-
AtomicLongFieldUpdater
-
AtomicReferenceFieldUpdater
根據類別名稱,就可以知道類別分別對應 int long 和一般物件。
舉一個例子,原本有一個 Candidate 物件以下稱為候選人,候選人有一個 score
屬性,有 10000 個選民(Thread)會隨機投票給這個候選人,每當投一票,score 就會 + 1。
在起初設計程式時,沒有考慮到 Thread Safe 問題,所以需要在不改動原本 score
的 int 型態同時,我們需要滿足 Thread Safe 需求,於是使用到了 AtomicIntegerFieldUpdater:
public class AtomicIntegerUpdateDemo {
public static class Candidate{
int id;
volatile int score; // 被 AtomicIntegerFieldUpdater 修飾的變數必須是 volatile
}
public final static AtomicIntegerFieldUpdater<Candidate> scoreUpdater =
AtomicIntegerFieldUpdater.newUpdater(Candidate.class, "score");
public static AtomicInteger allScore = new AtomicInteger(0);
public static void main(String[] args) throws InterruptedException {
final Candidate stu = new Candidate();
Thread[] threads = new Thread[10000];
for(int i = 0; i < 10000; ++i){
threads[i] = new Thread(() -> {
if(Math.random() > 0.4) {
scoreUpdater.incrementAndGet(stu);
allScore.incrementAndGet(); // 同時與 Candidate 累加,後面驗證正確性用。
}
});
threads[i].start();
}
for (int i = 0; i < 10000; i++) {
threads[i].join();
}
System.out.println("score: " + stu.score);
System.out.println("Allscore: " + allScore);
}
}
印出結果:
score: 6004
Allscore: 6004
!!! AtomicFieldUpdater 有 3 個使用上需要注意點:
-
Updater 們只能修改可見範圍的變數,他使用的是反射原理,如果變數不可見(如
private
),就沒辦法 work 了。 -
為了保證變數被正確讀出,變數必須被宣告為
volatile
。如果原本的類別沒有宣告為volatile
,加上去就好了,並不會有甚麼問題。 -
由於 CAS 操作會通過物件內屬性的相對偏移量進行賦值,說了是 物件 因此他不支持類別屬的
static
變數(Unsafe.objectFieldOffset()
不支持靜態變數)。