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

Java 線程核心方法深度對比:wait、notify、sleep、interrupt 機制全解析

     編輯:Java 相關知識

在 Java 多線程開發中,wait()、notify()、sleep()、interrupt() 是線程協作與控制的基石。混淆它們的行為差異是導致死鎖、活鎖、線程饑餓等併發 Bug 的常見原因。本文從鎖行為、喚醒機制、適用場景等維度進行系統性對比,助你構建清晰的線程控制心智模型。

一、 方法歸屬與基本語義速覽

Thread 類(靜態方法為主)

  • sleep(long millis):暫停當前線程,指定時間後自動恢復運行狀態,不釋放鎖。
  • yield():暫停當前線程,主動讓出 CPU 時間片,進入就緒狀態,不釋放鎖,可能立即再次獲取調度。
  • join():等待目標線程終止,底層調用 wait(0) 實現。
  • interrupt():設置中斷標誌位,不直接停止線程。若目標線程處於阻塞狀態(wait/sleep/join/I/O),將拋出 InterruptedException 或 ClosedByInterruptException 並清除標誌位。

Object 類(實例方法,需持有監視器鎖)

  • wait():釋放當前對象鎖,進入等待集合,直到 notify/notifyAll、超時或中斷被喚醒,重新競爭鎖後繼續執行。
  • notify():隨機喚醒一個在該對象上等待的線程。
  • notifyAll():喚醒所有在該對象上等待的線程。

二、 核心對比:sleep VS wait

維度 sleep() wait()
所屬類 Thread(靜態) Object(實例)
鎖行為 不釋放 同步鎖 完全釋放 對象鎖
喚醒方式 時間到期 / interrupt() 時間到期 / notify/notifyAll / interrupt()
調用前提 無特殊要求 必須在 synchronized 同步塊/方法中,持有對象監視器
典型場景 線程內部節奏控制(延遲、輪詢間隔) 線程間通信(生產者-消費者、狀態變更通知)
異常處理 拋出 InterruptedException,需捕獲/拋出 拋出 InterruptedException,需捕獲/拋出
// sleep 簽名
public static void sleep(long millis) throws InterruptedException
public static void sleep(long millis, int nanos) throws InterruptedException

// wait 簽名
public final void wait() throws InterruptedException
public final void wait(long timeout) throws InterruptedException
public final void wait(long timeout, int nanos) throws InterruptedException

三、 wait/notify 協作模式的關鍵規則

  1. 監視器所有權:調用 wait/notify/notifyAll 的線程必須是該對象監視器的擁有者(即在 synchronized(obj) 內部),否則拋出 IllegalMonitorStateException。
  2. 虛假喚醒:線程可能在無通知、無中斷、無超時情況下意外返回。最佳實踐:在循環中調用 wait(),檢查條件謂詞。
    synchronized (lock) {
        while (!condition) { // 而非 if
            lock.wait();
        }
        // 執行業務邏輯
    }
  3. 中斷與鎖重獲取:調用 interrupt() 後,線程會先嘗試重新獲取對象鎖,獲得鎖後才拋出 InterruptedException。

四、 interrupt() 機制深度解析

核心原理:interrupt() 僅修改線程的中斷標誌位(boolean 型),不會強制終止線程。真正的中斷響應依賴於線程自身代碼的配合:

  • 阻塞方法(wait/sleep/join/可中斷 I/O):內部持續檢查中斷標誌,發現為 true 時拋出 InterruptedException 並清除標誌位。
  • 非阻塞運行狀態:調用 interrupt() 僅設置標誌位,線程不會自動停止,需通過 Thread.interrupted() 或 isInterrupted() 主動輪詢並處理。

最佳實踐:捕獲 InterruptedException 後,應重新設置中斷標誌(Thread.currentThread().interrupt())或向上拋出,保持中斷語義傳遞,避免吞噬中斷導致調用方無法感知。

五、 yield() 讓步機制補充

  • 從 Running → Runnable,不釋放鎖。
  • 僅是向調度器提示「我願意讓出 CPU」,調度器可能忽略,線程極大概率立即再次被調度。
  • 現代 JVM 中極少直接使用,通常由高級併發工具(如 LockSupport.park())替代。

六、 總結速查表

| 方法 | 釋放鎖 | 喚醒條件 | 適用層級 | 核心用途 | |------|--------|----------|----------|----------| | sleep | ❌ | 時間到 / 中斷 | 線程級 | 定時延遲、節奏控制 | | wait | ✅ | 通知 / 時間到 / 中斷 | 對象級 | 線程間協作、條件等待 | | notify | ❌ | — | 對象級 | 單線程喚醒(需謹慎防漏喚醒) | | notifyAll | ❌ | — | 對象級 | 廣播喚醒(推薦默認選擇) | | interrupt | ❌ | 設置標誌位 | 線程級 | 取消任務、打破阻塞 | | yield | ❌ | 調度器決定 | 線程級 | 禮讓 CPU(極少用) |

七、 實戰建議

  1. 優先使用 java.util.concurrent 包下的高級組件(CountDownLatch、CyclicBarrier、Condition、BlockingQueue),減少直接操作底層 wait/notify。
  2. 若必須使用 wait/notify,永遠在 while 循環中檢查條件,使用 notifyAll 替代 notify 避免信號丟失。
  3. 建立統一的中斷處理規範:不吞噬中斷、及時響應、資源正確釋放。
  4. 避免在持有鎖期間調用 sleep 或耗時操作,降低鎖競爭粒度。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved