多線程並發核心挑戰在於線程同步(控制執行順序)與線程通信(共享數據傳遞)。Java 提供 synchronized、volatile、wait/notify 三大基石。本文通過單例模式破壞、生產消費無序兩個反面教材,引出鎖機制正確用法,並構建完整的生產者-消費者模型。
class MySingle {
private static MySingle instance = null;
private MySingle() {}
public static MySingle getInstance() {
if (instance == null) { instance = new MySingle(); } // 非原子操作!
return instance;
}
}
現象:10 條線程並發調用,產生多個實例(hashCode 不一致)
根因:instance == null 檢查與 new MySingle() 非原子,多線程同時通過檢查
class Factory {
public void createProduct(int i) { for (j=1..100) print; }
public void sellProduct(int i) { for (j=1..20) print; }
}
現象:生產線程與銷售線程輸出交織混亂,無法保證「生產一輪 → 銷售一輪」順序
synchronized:互斥鎖 + 內存屏障(刷新工作內存到主存、失效其他線程緩存)volatile:僅保證可見性與有序性,不保證原子性
public static MySingle getInstance() {
if (instance == null) { // 第一次檢查(無鎖,性能)
synchronized (MySingle.class) { // 類級別鎖
if (instance == null) { // 第二次檢查(有鎖,安全)
instance = new MySingle(); // volatile 禁止指令重排 (JDK 5+)
}
}
}
return instance;
}
private static volatile MySingle instance = null; // 關鍵!
class Factory {
private boolean isProducing = true; // true=生產中, false=銷售中
public synchronized void createProduct(int round) {
while (!isProducing) { // 條件不滿足 → 等待
try { this.wait(); } catch (InterruptedException e) { e.printStackTrace(); }
}
for (int j = 1; j <= 100; j++) {
System.out.println("第" + round + "輪生產,產出" + j + "件");
}
isProducing = false; // 狀態翻轉
this.notify(); // 喚醒消費者
}
public synchronized void sellProduct(int round) {
while (isProducing) {
try { this.wait(); } catch (InterruptedException e) { e.printStackTrace(); }
}
for (int j = 1; j <= 20; j++) {
System.out.println("第" + round + "輪銷售,銷售" + j + "件");
}
isProducing = true;
this.notify(); // 喚醒生產者
}
}
關鍵點:
synchronized 修飾方法 → 隱式鎖 thiswhile 而非 if 判斷條件 → 防止虛假喚醒wait() 釋放鎖並進入等待隊列;notify() 喚醒一個等待線程isProducing 受鎖保護,保證可見性class NumberRange {
private volatile int counter = 0;
public void increment() { counter++; } // 非原子!讀-改-寫三步
}
編譯器視角等價代碼:
public synchronized int getCounter() { return counter; }
public synchronized void setCounter(int v) { counter = v; }
public int increment() {
int temp = getCounter(); // 讀
temp = temp + 1; // 改
setCounter(temp); // 寫
return temp;
}
結論:volatile 只能保證單次讀/寫可見,**複合操作(如 ++、+=)仍需 synchronized 或 AtomicInteger**
class FactoryLock {
private final Lock lock = new ReentrantLock();
private final Condition notFull = lock.newCondition();
private final Condition notEmpty = lock.newCondition();
private final Queue queue = new LinkedList<>();
private final int capacity = 100;
public void produce(int item) throws InterruptedException {
lock.lock();
try {
while (queue.size() == capacity) notFull.await();
queue.offer(item);
notEmpty.signal();
} finally { lock.unlock(); }
}
public int consume() throws InterruptedException {
lock.lock();
try {
while (queue.isEmpty()) notEmpty.await();
int item = queue.poll();
notFull.signal();
return item;
} finally { lock.unlock(); }
}
}
優勢:多條件變量精準喚醒、可中斷鎖、公平鎖選項、超時獲取鎖
| 場景 | 推薦方案 | 避坑 |
|---|---|---|
| 簡單互斥/狀態翻轉 | synchronized 方法/塊 | 鎖粒度過大(鎖整個方法而非關鍵區) |
| 單一變量可見性 | volatile + 原子操作 | 用於複合操作 (count++) |
| 計數器/序列號 | AtomicLong / LongAdder | 自行 synchronized 實現 |
| 生產消費/有界隊列 | ArrayBlockingQueue / Lock+Condition | 手寫 wait/notify 易漏喚醒 |
| 讀多寫少 | ReentrantReadWriteLock | 寫多場景性能反而下降 |
「並發編程的藝術在於:用最小的鎖,保護最關鍵的數據,實現最大程度的並行。」