數據同步是多線程編程的核心難點。本文從「為什麼需要同步」切入,對比 synchronized 與 volatile 兩大基石機制的適用邊界。
public class CharacterDisplayCanvas extends JComponent implements CharacterListener {
private char[] tmpChar = new char[1];
public synchronized void newCharacter(CharacterEvent ce) {
tmpChar[0] = (char) ce.character;
repaint(); // 觸發 paintComponent()
}
protected synchronized void paintComponent(Graphics gc) {
// 讀取 tmpChar[0] 繪製
}
}
race condition:newCharacter()(事件線程)與 paintComponent()(EDT 線程)並發訪問 tmpChar,且操作非原子(寫入 → 觸發重繪 vs 讀取 → 繪製)。
private volatile boolean done = false; // 標記位
public void run() {
while (!done) { /* ... */ } // 每次從主存讀取
}
public void setDone() { done = true; } // 寫入即時刷新主存
long/double)為原子操作// 錯誤示範:死鎖陷阱
public synchronized void run() { while (!done) { /* ... */ } }
public synchronized void setDone() { done = true; }
run() 持有鎖不釋放 → setDone() 永無法獲取鎖 → 死鎖。鎖範圍過大是死溫床。
count++(讀-改-寫)非原子if (a > 0) b = 1;)volatile int[] arr 僅保證引用可見,元素讀寫無保障。需用 AtomicIntegerArray 或鎖保護。
private int done;
public synchronized int getDone() { return done; }
public synchronized void setDone(int v) { done = v; }
通過同步 getter/setter 間接實現 volatile 效果,但引入鎖開銷,不如 volatile 優雅。
下文將延續計分板案例,實戰同步策略演進。