承接前文,通過計分板組件 ScoreLabel 實戰演示多線程環境下的同步策略演進。
public class ScoreLabel extends JLabel implements CharacterListener {
private volatile int score = 0;
private int char2type = -1;
private CharacterSource generator, typist;
public synchronized void newCharacter(CharacterEvent ce) {
if (ce.source == generator) { // 產生字符
if (char2type != -1) { score--; setScore(); }
char2type = ce.character;
} else { // 用戶輸入
if (char2type != ce.character) score--;
else { score++; char2type = -1; }
setScore();
}
}
// resetGenerator/resetTypist/resetScore/setScore 均同步
}
痛點:newCharacter() 由多線程並發調用,涉及多變量(score、char2type)的複合操作,volatile 單變量可見性無法解決「檢查-執行」原子性問題。
this(實例鎖)Class 對象(類鎖)非同步方法繞過鎖檢查,可任意時間執行 → 防衛性設計:所有訪問共享狀態的方法均應同步。
public void newCharacter(CharacterEvent ce) {
if (ce.source == generator) {
synchronized (this) { /* 生產者邏輯 */ }
} else {
synchronized (this) { /* 消費者邏輯 */ }
}
}
或鎖定共享數據對象而非 this,進一步降低爭用。
private Lock scoreLock = new ReentrantLock();
public void newCharacter(CharacterEvent ce) {
if (ce.source == generator) {
scoreLock.lock();
try { /* 生產者邏輯 */ } finally { scoreLock.unlock(); }
} else {
scoreLock.lock();
try { /* 消費者邏輯 */ } finally { scoreLock.unlock(); }
}
}
Lock,單物件持有多把鎖tryLock() / tryLock(timeout) 避免無限等待new ReentrantLock(true) 保證 FIFO 獲取,防飢餓private synchronized void newGeneratorCharacter(int c) { /* ... */ }
private synchronized void newTypistCharacter(int c) { /* ... */ }
public synchronized void newCharacter(CharacterEvent ce) {
if (ce.source == generator) newGeneratorCharacter(ce.character);
else newTypistCharacter(ce.character);
}
注意:私有策略方法仍加 synchronized,防止未來維護者誤調用(嵌套鎖機制保證同一線程重入不阻塞)。
ReentrantLock 支援 getHoldCount()、isHeldByCurrentThread()、getQueueLength() 監控| 場景 | 推薦方案 |
|---|---|
| 簡單同步、方法級粒度 | synchronized 方法 |
| 需縮小鎖範圍、高併發熱點 | synchronized 塊 |
| 跨方法鎖範圍、需 tryLock/公平性/多條件 | Lock 接口 |
| 靜態+非靜態方法混合同步 | Lock 更直觀(不綁定 this/Class) |