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

Java 多執行緒:為什麼需要資料同步?從「髒資料」到原子操作

     編輯:Java 相關知識

變數分類與共享風險

  • 區域變數:方法內定義,執行緒私有,無共享問題。
  • 類變數(static):屬於類,所有執行緒共享,必須同步。

若所有存取都是「讀取」,通常不需同步;只要有「寫入」,就必須保證同一時間只有一個執行緒操作。

經典「髒資料」示範


public class DirtyReadDemo extends Thread {
    public static int n = 0;

    public void run() {
        int m = n;   // 讀取
        Thread.yield(); // 主動讓出 CPU,放大競態機率
        m++;
        n = m;       // 寫回
    }

    public static void main(String[] args) throws Exception {
        DirtyReadDemo demo = new DirtyReadDemo();
        Thread[] threads = new Thread[100];
        for (int i = 0; i < 100; i++) threads[i] = new Thread(demo);
        for (Thread t : threads) t.start();
        for (Thread t : threads) t.join();
        System.out.println("n = " + n); // 預期 100,實際常 < 100
    }
}

輸出範例:n = 59。

為什麼 n++ 也不是原子的?

Java 編譯器將 n++ 編譯為多條 bytecode:


aload_0        // 載入 this
dup            // 複製引用
getfield       // 讀取 n
iconst_1       // 常數 1
iadd           // 整數加法
putfield       // 寫回 n

對應「複製到工作記憶體 → 運算 → 寫回主記憶體」三步驟,中間任何一步被切換都會產生競態。

Java 記憶體模型(JMM)視角

  • 主記憶體:儲存所有共享變數(heap)。
  • 工作記憶體:每條執行緒私有,儲存共享變數的副本(stack)。

執行緒操作共享變數流程:主記憶體 → 工作記憶體 → 運算 → 寫回主記憶體。無同步時,副本何時刷新不可預期,導致可見性與原子性問題。

解決方案:同步與原子操作

最佳實務建議

  1. 優先使用 java.util.concurrent.atomic 包下的原子類別。
  2. 必須用鎖時,盡量縮小同步區塊範圍(synchronized 區塊優於同步方法)。
  3. 共享變數聲明 final 或 volatile 視語意而定,但複合操作仍需鎖或原子類別。
  4. 避免在同步區塊內呼叫外部方法、耗時 I/O,降低鎖持有時間。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved
方案關鍵字/類別適用場景
同步方法synchronized 修飾方法整個方法需互斥
同步區塊synchronized(obj) { ... }縮小鎖範圍,提高並發
volatilevolatile 修飾變數僅保證可見性,不保證複合操作原子性
原子類別AtomicInteger 等無鎖 CAS,高併發計數器首選
LockReentrantLock需可中斷、公平鎖、條件變數等進階功能