首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

Java 線程安全陷阱:i++ 非原子性實證與 AtomicInteger 解決方案

     編輯:Java 相關知識

問題現場:計數器錯失更新

public class TestMain {
    int i = 0; // 共享計數器
    
    public static void main(String[] args) {
        TestMain c = new TestMain();
        Runnable worker = new Worker(c);
        for (int k = 0; k < 200; k++) new Thread(worker).start();
        
        while (true) {
            System.out.println(c.i); // 觀察值
            try { Thread.sleep(1000); } catch (InterruptedException ignored) {}
        }
    }
}

class Worker implements Runnable {
    TestMain c;
    Worker(TestMain c) { this.c = c; }
    public void run() {
        try { Thread.sleep((int)(Math.random() * 25)); } catch (InterruptedException ignored) {}
        c.i++; // 關鍵:非原子操作!
    }
}

運行結果

預期 200,實際僅約 50% 機率達標,其餘為 199、198... 並發寫入導致更新丟失。

根因分析:i++ 三步曲

  1. Read:從主記憶體讀取 i 到執行緒工作記憶體。
  2. Increment:執行緒局部遞增。
  3. Write:寫回主記憶體。

多執行緒交錯執行時,Read-Write 間隙導致彼此覆蓋對方結果。Java 不保證 ++ 原子性 (除非 volatile 配合 CAS 迴圈,但語法不支援)。

解決方案對比

方案代碼示例優點缺點
synchronizedsynchronized(c) { c.i++; }語法簡單、隱式鎖、JVM 優化成熟 (偏向鎖、輕量級鎖)爭用激烈時效能下降、死鎖風險
ReentrantLocklock.lock(); try { c.i++; } finally { lock.unlock(); }可中斷、公平鎖、條件變數、超時嘗試需手動 unlock、語法較繁瑣
AtomicInteger (JDK 5+)AtomicInteger i = new AtomicInteger(0); i.incrementAndGet();無鎖 CAS、高併發效能優、API 豐富 (addAndGet, compareAndSet)僅支援單一變數、ABA 問題極少數場景需注意
LongAdder (JDK 8+)LongAdder sum = new LongAdder(); sum.increment();高併發下優於 AtomicInteger (分段計數、最後聚合)記憶體佔用略高、無 getAndSet 等精確語義

最佳實踐建議

  1. 首選 AtomicInteger/LongAdder:簡潔、高效、語義明確,專為計數場景設計。
  2. 複雜臨界區用 synchronized:多變數邏輯、非計數型業務原子操作。
  3. 避免 volatile int i; i++;:volatile 僅保證可見性,不保證複合操作原子性。
  4. 不可變物件:從設計層面消除共享可變狀態 (String、Final 類、函數式風格)。

驗證代碼 (AtomicInteger 版)

import java.util.concurrent.atomic.AtomicInteger;

public class SafeCounter {
    static AtomicInteger count = new AtomicInteger(0);
    public static void main(String[] args) throws InterruptedException {
        Runnable task = () -> { 
            try { Thread.sleep((int)(Math.random()*25)); } catch (InterruptedException ignored) {}
            count.incrementAndGet(); 
        };
        Thread[] threads = new Thread[200];
        for (int k=0; k<200; k++) { threads[k] = new Thread(task); threads[k].start(); }
        for (Thread t : threads) t.join();
        System.out.println("Final: " + count.get()); // 穩定輸出 200
    }
}
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved