生產者-消費者是經典的並發協作模式。本文通過「單生產單消費」→「多生產多消費」的層層遞進,揭示 wait/notify 陷阱,最終給出生產級 while + notifyAll 正確實現。
共享資源類 Resource 使用 boolean flag 標記是否有產品,synchronized 保證原子性,wait() 釋放鎖等待,notify() 喚醒對方。
public class Resource {
private int number = 0;
private boolean flag = false;
public synchronized void create() {
if (flag) { try { wait(); } catch (InterruptedException e) { e.printStackTrace(); } }
number++;
System.out.println(Thread.currentThread().getName() + "生產者------------" + number);
flag = true;
notify();
}
public synchronized void destroy() {
if (!flag) { try { wait(); } catch (InterruptedException e) { e.printStackTrace(); } }
System.out.println(Thread.currentThread().getName() + "消費者****" + number);
flag = false;
notify();
}
}
測試類啟動一對線程,輸出完美交替:生產1 → 消費1 → 生產2 → 消費2 …
直接啟動 2 個 Producer + 2 個 Consumer,出現嚴重問題:
if 判斷只檢查一次,被 notify() 喚醒後不再複查條件,導致「虛假喚醒」繼續執行。notify() 隨機喚醒單個線程,可能喚醒同類線程(生產喚醒生產),造成死鎖或邏輯錯誤。if → while將條件判斷改為循環,喚醒後重新檢查:
while (flag) { wait(); } // 生產者
while (!flag) { wait(); } // 消費者
解決虛假喚醒,但引發新問題:程序運行到某值(如 74)卡死——死鎖。原因:notify() 可能喚醒同類,導致雙方都在等待對方。
notify → notifyAll喚醒所有等待線程,確保對方類型線程一定被喚醒:
notifyAll(); // 生產與消費雙方均改用 notifyAll
public class Resource {
private int number = 0;
private boolean flag = false;
public synchronized void create() {
while (flag) { try { wait(); } catch (InterruptedException e) { e.printStackTrace(); } }
number++;
System.out.println(Thread.currentThread().getName() + "生產者------------" + number);
flag = true;
notifyAll();
}
public synchronized void destroy() {
while (!flag) { try { wait(); } catch (InterruptedException e) { e.printStackTrace(); } }
System.out.println(Thread.currentThread().getName() + "消費者****" + number);
flag = false;
notifyAll();
}
}
運行結果:多線程下嚴格交替,無重複、無遺漏、無死鎖。
while 而非 if 檢查等待條件,防止虛假喚醒。notifyAll(),避免同類線程互喚導致死鎖。java.util.concurrent 包下的 BlockingQueue(如 ArrayBlockingQueue),更簡潔高效。