專案演進中,原本非執行緒安全的 volatile int 欄位突然面臨併發寫入需求。若逐一修改所有存取點,違反開閉原則(對擴充開放、對修改關閉),且工作量大、風險高。特別是許多場景僅為唯讀,無需同步。
java.util.concurrent.atomic 提供三種更新器,無需修改原欄位宣告,透過反射直接對實例欄位進行 CAS 操作:
AtomicIntegerFieldUpdater - int 欄位AtomicLongFieldUpdater - long 欄位AtomicReferenceFieldUpdater - 物件參考欄位public class AtomicIntegerFieldUpdaterDemo {
public static class Candidate {
int id;
volatile int score; // 必須 volatile
}
// 建立更新器:目標類別、欄位名稱
public static final AtomicIntegerFieldUpdater<Candidate> scoreUpdater =
AtomicIntegerFieldUpdater.newUpdater(Candidate.class, "score");
// 基準計數器(AtomicInteger 本身線程安全)
public static final AtomicInteger allScore = new AtomicInteger(0);
public static void main(String[] args) throws InterruptedException {
final Candidate candidate = new Candidate();
Thread[] threads = new Thread[10000];
// 模擬 10000 選民,60% 投贊成票
for (int i = 0; i < 10000; i++) {
threads[i] = new Thread(() -> {
if (Math.random() > 0.4) {
scoreUpdater.incrementAndGet(candidate); // 原子增
allScore.incrementAndGet();
}
});
threads[i].start();
}
// 等待所有執行緒完成
for (Thread t : threads) { t.join(); }
System.out.println("score = " + candidate.score);
System.out.println("allScore = " + allScore.get());
// 輸出兩者相等,證明線程安全
}
}
score = 5987
allScore = 5987
多次執行結果始終一致,證明 AtomicIntegerFieldUpdater 成功保障了普通 volatile int 欄位的原子性更新。
| 限制 | 說明 | 原因 |
|---|---|---|
| 可見性 | 欄位必須為更新器可見 | 內部用反射 Field.getDeclaredField(),private 欄位會失敗 |
| volatile | 欄位必須宣告為 volatile |
確保讀取可見性,CAS 需要最新值 |
| 非 static | 不支援 static 欄位 |
Unsafe.objectFieldOffset() 僅支援實例欄位 |
volatile 欄位需新增原子寫入AtomicInteger 更簡潔static 欄位 → 需其他方案更新器建立時透過反射獲取 Field,呼叫 Unsafe.objectFieldOffset(field) 取得欄位在物件中的記憶體偏移量。後續 CAS 操作直接對該記憶體位址進行比較交換,繞過 Java 存取檢查,實現無鎖原子更新。
來源:《實戰 Java 高併發程式設計》