java.lang.Object 中,每個對象天生擁有一把內部鎖和一個等待隊列。synchronized(obj) 塊或實例同步方法中)。否則拋 IllegalMonitorStateException。| 方法 | 行為 | 釋放鎖? | 喚醒後 |
|---|---|---|---|
wait() | 當前線程進入對象等待隊列,釋放鎖,阻塞直到被喚醒/中斷/超時 | ✅ 完全釋放 | 重新競爭鎖,獲得後從 wait() 返回 |
wait(long timeout) | 帶超時的等待,超時自動喚醒 | ✅ | 同無參版本 |
notify() | 喚醒任意一個在該對象等待隊列中的線程 | ❌ 不釋放(需退出同步塊) | 被喚醒線程進入鎖競爭隊列 |
notifyAll() | 喚醒所有在該對象等待隊列中的線程 | ❌ | 同上 |
經典生產者-消費者場景:
// 錯誤示範:無同步保護
if (buffer.isEmpty()) { // 1. 消費者檢查條件
// 上下文切換:生產者執行
buffer.add(item); // 2. 生產者添加元素
buffer.notifyAll(); // 3. 生產者通知(此時消費者還沒 wait!)
// 切回消費者
buffer.wait(); // 4. 消費者進入等待 → 永久阻塞!信號丟失
}
結論:「檢查條件」與「等待/通知」必須是原子操作,因此必須持有鎖:
synchronized (buffer) { // 持有鎖
while (buffer.isEmpty()) { // 條件不滿足
buffer.wait(); // 釋放鎖並等待,原子性完成
}
// 條件滿足,繼續執行,仍持有鎖
Item item = buffer.take();
}
public class SingleSlotBuffer {
private int value;
private boolean hasValue = false;
/** 消費者取走數據 */
public synchronized int take() {
while (!hasValue) { // while 防虛假喚醒
try { wait(); } // 釋放鎖等待
catch (InterruptedException e) { Thread.currentThread().interrupt(); }
}
hasValue = false;
notify(); // 喚醒生產者
return value;
}
/** 生產者放入數據 */
public synchronized void put(int value) {
while (hasValue) {
try { wait(); }
catch (InterruptedException e) { Thread.currentThread().interrupt(); }
}
this.value = value;
hasValue = true;
notify(); // 喚醒消費者
}
}
public class Producer implements Runnable {
private final SingleSlotBuffer buffer;
public Producer(SingleSlotBuffer buffer) { this.buffer = buffer; }
@Override public void run() {
for (int i = 0; ; i++) {
buffer.put(i);
System.out.println("Produced: " + i);
sleepRandom(100, 500);
}
}
private void sleepRandom(int min, int max) {
try { Thread.sleep((long)(Math.random()*(max-min+1)+min)); }
catch (InterruptedException e) { Thread.currentThread().interrupt(); }
}
}
public class Consumer implements Runnable {
private final SingleSlotBuffer buffer;
public Consumer(SingleSlotBuffer buffer) { this.buffer = buffer; }
@Override public void run() {
while (true) {
int val = buffer.take();
System.out.println("Consumed: " + val);
sleepRandom(100, 500);
}
}
private void sleepRandom(int min, int max) {
try { Thread.sleep((long)(Math.random()*(max-min+1)+min)); }
catch (InterruptedException e) { Thread.currentThread().interrupt(); }
}
}
public class Main {
public static void main(String[] args) {
SingleSlotBuffer buffer = new SingleSlotBuffer();
new Thread(new Producer(buffer), "Producer").start();
new Thread(new Consumer(buffer), "Consumer").start();
}
}
public class AlternatePrinter {
private static final Object LOCK = new Object();
private static int next = 1; // 1 或 2
public static void main(String[] args) {
new Thread(() -> printNum(1), "T1").start();
new Thread(() -> printNum(2), "T2").start();
}
static void printNum(int num) {
while (true) {
synchronized (LOCK) {
while (next != num) { // 不是自己輪次 → 等待
try { LOCK.wait(); }
catch (InterruptedException e) { Thread.currentThread().interrupt(); return; }
}
System.out.println(Thread.currentThread().getName() + ": " + num);
next = (num == 1) ? 2 : 1; // 切換輪次
LOCK.notifyAll(); // 喚醒另一線程
}
}
}
}
關鍵點:while 循環檢查條件(防虛假喚醒)、執行完業務邏輯先修改共享狀態再 notifyAll、使用同一把鎖 LOCK。
JVM 規範允許線程在無 notify、無中斷、無超時情況下自發從 wait() 返回(虛假喚醒)。因此必須用 while (conditionNotMet) wait(); 而非 if,醒來後重新檢查條件,不滿足繼續等待。
| 維度 | Object.wait/notify | java.util.concurrent (Lock/Condition) |
|---|---|---|
| API 層級 | 底層、面向對象鎖 | 高層、顯式鎖/條件變量 |
| 多條件支持 | 一個對象一個等待隊列 | 一個 Lock 可綁定多個 Condition |
| 可中斷等待 | 不支持(只能中斷線程) | condition.awaitInterruptibly() |
| 公平性控制 | 無 | ReentrantLock(true) 公平鎖 |
| 超時精度 | 毫秒/納秒 | 納秒,支持 TimeUnit |
| 編程複雜度 | 低(隱式鎖) | 中(顯式鎖/解鎖) |
| 推薦場景 | 簡單同步、教學演示 | 生產級複雜同步邏輯 |
BlockingQueue、Semaphore、CountDownLatch、CompletableFuture 等,減少手動同步代碼。wait/notify/notifyAll 是 Java 并發的基石,理解「鎖保護狀態、等待隊列管理線程、條件檢查與等待原子化」三位一體的設計,能寫出正確的生產者-消費者、交替打印等經典模式。實戰中,簡單場景可用,復雜場景請擁抱 java.util.concurrent。