Java 變數分為區域變數(執行緒私有,無同步問題)與類變數/實例變數(堆上共享,需同步)。當多執行緒對共享變數進行「讀-改-寫」非原子操作時,會發生髒資料。
public class MyThread extends Thread {
public static int n = 0; // 共享類變數
public void run() {
int m = n; // 讀取
yield(); // 主動讓出 CPU,擴大競態窗口
m++; // 修改區域副本
n = m; // 寫回
}
public static void main(String[] args) throws Exception {
Thread[] threads = new Thread[100];
for (int i = 0; i < threads.length; i++) {
threads[i] = new Thread(new MyThread());
threads[i].start();
}
for (Thread t : threads) t.join();
System.out.println("n = " + n); // 預期 100,實際常見 59~99
}
}
執行緒 A 執行緒 B
│ │
├─ m = n (讀到 0) │
├─ yield() 讓出 CPU │
│ ├─ m = n (讀到 0)
│ ├─ yield()
│ ├─ m++ → 1
│ ├─ n = 1
├─ (恢復) m++ → 1 │
├─ n = 1 (覆蓋 B 的結果!) │
│ │
結果:n=1,少算了 1 次
n++ 都不是原子操作!位元碼證明Java 原始碼: n++;
Javap 反編譯位元碼:
0: getstatic #2 // 讀取 n 值到操作數堆疊
3: iconst_1 // 推入常數 1
4: iadd // 整數相加
5: putstatic #2 // 寫回 n
8: return
// 三步驟:讀取 → 計算 → 寫回,中間任何時刻被切換都會丟失更新
| 方案 | 關鍵字/類別 | 原理 | 適用場景 | 效能 |
|---|---|---|---|---|
| synchronized 方法 | synchronized | 鎖住 this / Class 物件,進入需取得監視器鎖 | 簡單同步、粒度較粗 | 中 |
| synchronized 區塊 | synchronized(obj) | 鎖住指定物件,縮小臨界區 | 高併發、精細控制 | 中高 |
| volatile | volatile | 強制從主記憶體讀寫,禁止指令重排,**不保證複合操作原子性** | 單一變數狀態旗標、雙重檢查鎖 | 高 |
| 原子類別 | AtomicInteger 等 | CAS 無鎖演算法,硬體級原子指令 | 計數器、序號生成、狀態更新 | 高 |
| Lock 介面 | ReentrantLock | 顯式鎖,支援公平/非公平、可中斷、超時、條件變數 | 複雜同步邏輯、需嘗試獲取鎖 | 中高 |
public static synchronized void inc() { n++; }
public void run() { for(int i=0;i<10;i++) inc(); }
public static AtomicInteger n = new AtomicInteger(0);
public void run() { for(int i=0;i<10;i++) n.incrementAndGet(); }
public static volatile int n = 0;
public void run() {
for(int i=0;i<10;i++) {
int cur, next;
do { cur = n; next = cur + 1; }
while (!UNSAFE.compareAndSwapInt(this, offset, cur, next)); // 需 Unsafe
}
}
volatile int n = 0;
// n = n + 1; // 非原子!讀取 n、加 1、寫回中間可被打斷
// n++; // 同上
// 只有單一讀/寫(n = 5; int x = n;)才具原子性
final、String、Guava Immutable 集合,根除共享狀態。Atomic*、ConcurrentHashMap、CopyOnWriteArrayList、ThreadLocal。volatile boolean shutdown)。final、volatile、安全發布模式(靜態初始化、volatile 參考、ThreadLocal)。核心口訣:共享可變狀態是萬惡之源;能不共享就不共享,能不可變就不可變,必須共享可變時,選對同步工具,守住原子性與可見性。