首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

Java 多線程核心:sleep() 與 wait() 區別深度解析、鎖釋放機制與線程狀態流轉

     編輯:Java 相關知識

前言

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 後繼續
典型場景定時任務、限流、簡單延遲生產者消費者、條件等待、線程協作

二、底層機制深度剖析

2.1 sleep():不釋放鎖的「自旋式」休眠

  • JVM 將線程狀態標記為 TIMED_WAITING
  • 線程保持持有的所有監視器鎖
  • 其他線程嘗試進入同步塊會阻塞
  • 時間到或 interrupt() 觸發 InterruptedException,線程恢復運行

2.2 wait():釋放鎖的「條件變量」語義

  • 前置條件:線程持有對象鎖 (在 synchronized 內)
  • 調用瞬間:原子性釋放鎖並進入對象 Wait Set
  • 線程狀態:WAITING (無超時) / TIMED_WAITING (有超時)
  • 其他線程可獲取鎖進入同步塊執行
  • 喚醒路徑:
    1. notify():隨機喚醒一個 Wait Set 線程 → 移入 Entry Set 競爭鎖
    2. notifyAll():喚醒所有 Wait Set 線程 → 全部進入 Entry Set
    3. 超時/中斷:自動移入 Entry Set
  • 獲取鎖後從 wait() 返回繼續執行

三、經典生產者消費者模式對比

3.1 錯誤示範:sleep 導致鎖佔用


// 錯誤:消費者 sleep 期間持有鎖,生產者無法放入數據
synchronized(queue) {
    while (queue.isEmpty()) {
        Thread.sleep(1000); // 持有鎖睡眠!
    }
    consume(queue.remove());
}

3.2 正確示範:wait 釋放鎖實現協作


// 正確:消費者 wait 釋放鎖,生產者可獲取鎖添加數據
synchronized(queue) {
    while (queue.isEmpty()) {
        queue.wait(); // 釋放鎖等待
    }
    consume(queue.remove());
}

// 生產者
synchronized(queue) {
    queue.add(item);
    queue.notify(); // 喚醒一個消費者
}

四、Java.util.Timer 內部實現揭秘

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() 將無法獲取鎖添加任務,導致任務調度延遲甚至丟失。

五、interrupt() 統一中斷機制

  • sleep()/wait()/join() 皆可被 interrupt() 打斷
  • 被打斷線程拋出 InterruptedException (自拋出,非 interrupt() 拋出)
  • 正常代碼執行中 interrupt() 僅設置中斷標誌,不拋異常
  • 標準停止模式:
    
    try {
        obj.wait(); // 或 sleep/join
    } catch (InterruptedException e) {
        Thread.currentThread().interrupt(); // 保留中斷狀態
        return; // 安全退出
    }
    

六、常見面試陷阱

  1. wait() 可在非同步代碼調用? → 否,拋 IllegalMonitorStateException
  2. sleep(0) 與 yield() 區別? → sleep(0) 觸發調度器重新調度,可能立即返回;yield() 僅暗示調度器讓出 CPU,不保證調度
  3. notify() 喚醒線程立即運行? → 否,需重新競爭鎖,可能延遲
  4. wait() 會釋放所有鎖? → 否,僅釋放當前對象鎖,其他對象鎖保持持有

七、總結

sleep 是「獨占資源休息」,wait 是「釋放資源等待條件」。掌握鎖釋放差異,即可在線程協作、定時調度、資源池等場景做出正確選型。記住:協作用 wait/notify,獨占用 sleep,中斷靠 interrupt。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved