Java 線程協作核心在於 `Object` 監視器方法 (`wait`/`notify`/`notifyAll`) 與 `Thread` 靜態/實例方法 (`sleep`/`join`)。理解它們的語義差異、鎖行為、喚醒條件,是編寫正確並發代碼的前提。
| 方法 | 所屬 | 釋放鎖 | 喚醒條件 | 典型用途 |
|---|---|---|---|---|
| `wait()` | Object | ✓ 完全釋放 | `notify()`/`notifyAll` / 中斷 / 超時 | 條件等待、生產者消費者 |
| `notify()` | Object | ✗ (持有鎖退出同步區才釋放) | 隨機喚醒一個等待線程 | 單消費者場景 |
| `notifyAll()` | Object | ✗ | 喚醒所有等待線程 | 多消費者、狀態變更廣播 |
| `sleep(ms)` | Thread (static) | ✗ 不釋放鎖 | 時間到期 | 節流、延遲、定時輪詢 |
| `join()` | Thread (instance) | ✗ 不釋放鎖 | 目標線程終止 | 主線程等待子線程完成 |
synchronized (lock) {
while (conditionNotMet) { // 必須用 while 防止虛假喚醒
lock.wait(); // 釋放鎖,進入 WAITING 狀態
}
// 條件滿足,執行業務邏輯
}
class BoundedBuffer {
private final Queue queue = new LinkedList<>();
private final int capacity;
public BoundedBuffer(int capacity) { this.capacity = capacity; }
public synchronized void put(T item) throws InterruptedException {
while (queue.size() == capacity) {
wait(); // 滿了等待
}
queue.offer(item);
notifyAll(); // 喚醒消費者
}
public synchronized T take() throws InterruptedException {
while (queue.isEmpty()) {
wait(); // 空了等待
}
T item = queue.poll();
notifyAll(); // 喚醒生產者
return item;
}
}
// 靜態方法,作用於當前執行緒
Thread.sleep(1000); // 休眠 1 秒
Thread.sleep(1000, 500000); // 1 秒 500 萬納秒 (納秒部分四捨五入到毫秒)
// 實戰:輪詢間隔節流
while (!shutdown) {
doWork();
try { Thread.sleep(POLL_INTERVAL_MS); }
catch (InterruptedException e) { Thread.currentThread().interrupt(); break; }
}
注意:`sleep` 不釋放任何鎖,若在 `synchronized` 區塊中睡眠,其他線程無法進入該同步區!
// Thread.join() 內部簡化邏輯
public final synchronized void join(long millis) throws InterruptedException {
long base = System.currentTimeMillis();
long now = 0;
while (isAlive()) {
long delay = millis - now;
if (delay <= 0) break;
wait(delay); // 內部調用 Object.wait(timeout)
now = System.currentTimeMillis() - base;
}
}
即:`join()` = `while (thread.isAlive()) wait(timeout)`。目標線程終止時,JVM 會自動 `notifyAll()` 喚醒等待者。
List workers = Arrays.asList(t1, t2, t3);
workers.forEach(Thread::start);
for (Thread t : workers) {
try { t.join(); }
catch (InterruptedException e) { Thread.currentThread().interrupt(); }
}
System.out.println("All workers finished.");
// B 等待 A 洗澡
while (a.isAlive()) {
Thread.sleep(10000); // 睡 10 秒醒來看看
System.out.println("Waited " + (time += 10) + " min");
}
缺點:A 早洗完 B 仍睡到時間;A 晚洗完 B 反覆醒來檢查。
// B 直接 join A
a.start();
a.join(); // 閻王鎖定:A 死前不放行
System.out.println("B can bath now!");
優點:A 結束瞬間 B 精確被喚醒,零延遲、零 CPU 浪費。
class Bathroom {
private boolean occupied = false;
private String currentUser;
public synchronized void enter(String user) throws InterruptedException {
while (occupied) wait(); // 等待空閒
occupied = true;
currentUser = user;
}
public synchronized void leave() {
occupied = false;
notifyAll(); // 通知等待者
}
}
適用於:多條件、多狀態、需公平性/優先級的複雜協作。
| 傳統原語 | JUC 替代品 | 優勢 |
|---|---|---|
| synchronized + wait/notify | ReentrantLock + Condition | 多條件變數、可中斷鎖、公平鎖、超時嘗試鎖 |
| Thread.sleep | TimeUnit.MILLISECONDS.sleep / ScheduledExecutorService | 可取消、精確調度、非阻塞延遲 |
| Thread.join | CountDownLatch / CompletableFuture / ThreadPoolExecutor.awaitTermination | 多線程協調、異步組合、優雅關閉 |
`wait/notify` 是監視器模式的核心,解決「條件滿足前等待」;`sleep` 是時間切片讓渡,不涉及協作;`join` 是線程生命週期同步的特例。掌握它們的**鎖語義**、**喚醒機制**、**虛假喚醒防護**,是並發編程基本功。生產代碼應優先拥抱 `java.util.concurrent` 高級抽象,將底層原語封裝在庫內部,業務代碼表達「做什麼」而非「怎麼同步」。