驗證 非執行緒安全的 int++ 在高併發下的資料競爭嚴重性,並量測 AtomicInteger (CAS) 的效能代價,為並發計數器選型提供實證依據。
10010,000,600 (總計 10 億 次)CountDownLatch 確保同時啟動、全部結束System.nanoTime()public class AtomicVsIntBenchmark {
static AtomicInteger atomic = new AtomicInteger(0);
static int plain = 0;
@Test public void testAtomic() throws InterruptedException { runTest(() -> atomic.getAndIncrement()); }
@Test public void testPlainInt() throws InterruptedException { runTest(() -> plain++); }
private void runTest(Runnable inc) throws InterruptedException {
int threads = 100, loops = 10_000_600;
CountDownLatch start = new CountDownLatch(1), end = new CountDownLatch(threads);
for (int i = 0; i < threads; i++) new Thread(() -> {
try { start.await(); } catch (InterruptedException ignored) {}
for (int j = 0; j < loops; j++) inc.run();
end.countDown();
}).start();
long t0 = System.nanoTime();
start.countDown(); end.await();
long ms = (System.nanoTime() - t0) / 1_000_000;
System.out.printf("%s: 耗時 %d ms, 結果 %d (預期 %d)%n",
inc.toString(), ms, (inc.toString().contains("Atomic") ? atomic.get() : plain),
(long)threads * loops);
}
}
| 實作 | 耗時 | 最終值 | 正確性 | 相對效能 |
|---|---|---|---|---|
AtomicInteger.getAndIncrement() | ~85,000 ms | 1,000,060,000 | ✅ 正確 | 1x (基準) |
int++ (非同步) | ~1,400 ms | ~119,428,988 | ❌ 嚴重資料遺失 | ~60x 較快 |
int++ 編譯為 READ → INC → WRITE 三指令,多執行緒交錯導致大量更新遺失(實測僅完成 ~12% 預期值)。AtomicInteger:CAS 迴圈 + volatile 記憶體屏障 + 緩存行鎖定 (MESI 協議)。int:單執行緒下 JIT 可優化為暫存器操作,極快但不安全。int++ 10 億次 / 1.4s ≈ 7.1G ops/s → ~1.1 cycles/op (JIT 極致優化)。AtomicInteger 10 億次 / 85s ≈ 11.8M ops/s → ~680 cycles/op (含 CAS 重試、緩存同步)。需要跨執行緒共享計數器?
├── 否 → 直接用 int / long (最快)
└── 是 → 更新頻率如何?
├── 低頻 (< 10K ops/s) → AtomicInteger / LongAdder (JDK 8+)
├── 高頻、低爭用 → LongAdder (分段計數,最終 sum())
└── 高頻、高爭用 → 考慮 ThreadLocal 計數 + 定期聚合 / Disruptor RingBuffer
內部維護 base + Cell[],低爭用時走 base (CAS),高爭用時分散到不同 Cell 減少緩存行跳躍,最終 sum() 聚合。適合統計計數、監控指標等「寫多讀少」場景。
實測鐵證:非同步 int++ 在併發下絕對不可用;AtomicInteger 正確但有代價;LongAdder 是高併發計數的現代最佳解。選型前請先問:「真的需要跨執行緒共享嗎?」