Thread.sleep() 與 Object.wait() 同為讓線程暫停的手段,但底層機制、鎖行為、適用場景截然不同。本文從 JVM 實現、監視器鎖、線程狀態機、實戰陷阱四個維度深度對比。
| 維度 | Thread.sleep(long ms) | Object.wait(long ms) |
|---|---|---|
| 所屬類 | Thread (靜態方法) | Object (實例方法) |
| 調用前提 | 無 | 必須在 synchronized 同步塊/方法中 |
| 鎖行為 | 不釋放任何鎖 | 釋放當前對象監視器鎖 |
| 喚醒方式 | 時間到期 / interrupt() | 時間到期 / notify() / notifyAll() / interrupt() |
| 喚醒後狀態 | 直接進入 Runnable/Running | 進入 Entry Set 競爭鎖,獲取鎖後從 wait 後繼續 |
| 典型場景 | 定時任務、限流、簡單延遲 | 生產者消費者、條件等待、線程協作 |
TIMED_WAITINGinterrupt() 觸發 InterruptedException,線程恢復運行WAITING (無超時) / TIMED_WAITING (有超時)notify():隨機喚醒一個 Wait Set 線程 → 移入 Entry Set 競爭鎖notifyAll():喚醒所有 Wait Set 線程 → 全部進入 Entry Setwait() 返回繼續執行
// 錯誤:消費者 sleep 期間持有鎖,生產者無法放入數據
synchronized(queue) {
while (queue.isEmpty()) {
Thread.sleep(1000); // 持有鎖睡眠!
}
consume(queue.remove());
}
// 正確:消費者 wait 釋放鎖,生產者可獲取鎖添加數據
synchronized(queue) {
while (queue.isEmpty()) {
queue.wait(); // 釋放鎖等待
}
consume(queue.remove());
}
// 生產者
synchronized(queue) {
queue.add(item);
queue.notify(); // 喚醒一個消費者
}
JDK Timer 使用 wait(timeout) 而非 sleep() 的核心原因:
private void mainLoop() {
while (true) {
synchronized(queue) {
// 計算下一任務執行時間
long executionTime = ...;
if (!taskFired) {
queue.wait(executionTime - currentTime); // 釋放鎖等待
}
}
}
}
// 並發添加任務
public void schedule(TimerTask task, long time) {
synchronized(queue) { // 能獲取鎖是因為 mainLoop 在 wait 時釋放了鎖
queue.add(task);
queue.notify(); // 喚醒 mainLoop 重新計算最近任務
}
}
關鍵洞察:若 mainLoop 用 sleep(),schedule() 將無法獲取鎖添加任務,導致任務調度延遲甚至丟失。
sleep()/wait()/join() 皆可被 interrupt() 打斷InterruptedException (自拋出,非 interrupt() 拋出)interrupt() 僅設置中斷標誌,不拋異常
try {
obj.wait(); // 或 sleep/join
} catch (InterruptedException e) {
Thread.currentThread().interrupt(); // 保留中斷狀態
return; // 安全退出
}
IllegalMonitorStateExceptionsleep 是「獨占資源休息」,wait 是「釋放資源等待條件」。掌握鎖釋放差異,即可在線程協作、定時調度、資源池等場景做出正確選型。記住:協作用 wait/notify,獨占用 sleep,中斷靠 interrupt。