首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 更多程式語言 >> 程式設計綜合問答

Java 多線程 join() 導致計數器結果錯誤:競態條件與同步修正全解析

     編輯:程式設計綜合問答

問題代碼回顧

public class JoinThread extends Thread {
    public static volatile int n = 0;
    public void run() {
        for (int i = 0; i < 10; i++, n++) { // n++ 非原子操作!
            try { sleep(2); } catch (Exception e) {}
        }
    }
    public static void main(String[] args) throws Exception {
        Thread[] threads = new Thread[100];
        for (int i = 0; i < threads.length; i++) threads[i] = new JoinThread();
        for (Thread t : threads) t.start();
        for (Thread t : threads) t.join(); // 僅等待執行緒結束
        System.out.println("n=" + JoinThread.n); // 預期 1000,實際 < 1000
    }
}

錯誤根因

  1. n++ 包含「讀取、遞增、寫回」三步,volatile 僅保證可見性,**不保證原子性**。
  2. 100 條執行緒並發執行 run(),極大概率發生「讀取舊值 → 遞增 → 覆寫」導致更新丟失。
  3. join() 僅阻塞主執行緒直到目標執行緒結束,**無法修正已發生的資料競爭**。

修正方案對比

方案程式碼片段優點缺點
synchronized 方法public synchronized void run() { ... }簡單、鎖粒度大並發度低,序列化執行
synchronized 區塊synchronized(JoinThread.class) { n++; }鎖粒度較小仍有鎖爭用
AtomicInteger (推薦)static AtomicInteger n = new AtomicInteger();
n.incrementAndGet();
無鎖、高效、語義清晰僅支援基本原子操作
LongAdder (高併發首選)static LongAdder n = new LongAdder();
n.increment();
分段計數、極低爭用最終一致性、Java 8+

最佳實踐版本

public class JoinThread extends Thread {
    private static final LongAdder counter = new LongAdder();

    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            counter.increment();
            try { Thread.sleep(2); } catch (InterruptedException ignored) {}
        }
    }

    public static void main(String[] args) throws InterruptedException {
        Thread[] threads = new Thread[100];
        for (int i = 0; i < threads.length; i++) threads[i] = new JoinThread();
        for (Thread t : threads) t.start();
        for (Thread t : threads) t.join();
        System.out.println("n=" + counter.sum()); // 穩定輸出 1000
    }
}

核心觀念總結

  • volatile ≠ 原子性;join() 只負責等待,不負責同步共享資料。
  • 計數器、累加器等熱點共享變數,**優先選用 java.util.concurrent.atomic 包類別**。
  • 若業務邏輯複雜需多變數一致性,再考慮 synchronized / ReentrantLock / STM。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved