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

Java 多線程核心:sleep() 與 wait() 區別深度解析(含 JDK 源碼註釋)

     編輯:Java 綜合教學

核心結論先行

維度Thread.sleep()Object.wait()
所屬類Thread(靜態方法)Object(實例方法)
核心目的線程暫停/控制執行節奏線程間通信/協作(等待通知機制)
鎖行為不釋放鎖(持有監視器所有權)釋放鎖(釋放監視器所有權)
調用前提無特殊要求必須在 synchronized 同步塊/方法中
喚醒方式時間到期自動喚醒 / interrupt() 中斷notify()/notifyAll() 喚醒 / 時間到期 / interrupt()
典型場景定時任務、限流延遲、模擬耗時操作生產者-消費者、線程池任務隊列、狀態等待

一、Thread.sleep() —— 線程「打盹」

JDK 官方文檔(JDK 17)

public static void sleep(long millis) throws InterruptedException
使當前正在執行的線程睡眠(暫時停止執行)指定的毫秒數,受系統計時器和調度器精度影響。線程不會失去任何監視器的所有權。

關鍵特性

  • 靜態方法:作用於當前執行線程(Thread.currentThread()),不可指定其他線程睡眠
  • 不釋放鎖:若在 synchronized 塊中調用 sleep,其他線程無法進入同步塊
  • 拋出 InterruptedException:被中斷時拋出,需捕獲處理(通常恢復中斷狀態)
  • 精度不保證:受 OS 調度影響,實際睡眠時間 ≥ 指定時間

正確使用模式

public class ClockTask implements Runnable {
    @Override
    public void run() {
        while (!Thread.currentThread().isInterrupted()) {
            try {
                System.out.println(LocalTime.now() + " 報時");
                Thread.sleep(1000); // 每秒執行一次
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt(); // 恢復中斷狀態
                break; // 退出循環
            }
        }
    }
}

二、Object.wait() —— 線程「等待通知」

JDK 官方文檔(JDK 17)

public final void wait() throws InterruptedException
導致當前線程等待,直到另一線程調用該對象的 notify() 或 notifyAll()。當前線程必須擁有此對象的監視器。線程釋放監視器所有權並等待,直到被喚醒後重新獲取監視器繼續執行。

關鍵特性

  • 實例方法:調用鎖對象的 wait(),而非 Thread 對象
  • 必須在同步塊中:否則拋出 IllegalMonitorStateException
  • 釋放鎖:調用瞬間釋放監視器,允許其他線程進入同步塊
  • 三種喚醒路徑:notify()/notifyAll()、超時時間到、中斷
  • 虛假喚醒:極少數情況無通知自動醒來,**必須在循環中調用 wait()**

標準等待-通知模式(生產者-消費者核心)

public class BoundedBuffer<T> {
    private final List<T> buffer = new ArrayList<>();
    private final int capacity;

    public BoundedBuffer(int capacity) { this.capacity = capacity; }

    // 生產者調用
    public synchronized void put(T item) throws InterruptedException {
        while (buffer.size() == capacity) { // 必須用 while 防虛假喚醒
            wait(); // 釋放鎖,等待消費者消費
        }
        buffer.add(item);
        notifyAll(); // 喚醒消費者
    }

    // 消費者調用
    public synchronized T take() throws InterruptedException {
        while (buffer.isEmpty()) {
            wait(); // 釋放鎖,等待生產者生產
        }
        T item = buffer.remove(0);
        notifyAll(); // 喚醒生產者
        return item;
    }
}

三、經典比喻:電視機與遙控器

鎖 = 遙控器,線程 = 家庭成員,共享對象 = 電視機

  • sleep():A 拿著遙控器看電視,累了眯 10 分鐘(sleep),遙控器仍在 A 手中,別人換不了台
  • wait():A 拿著遙控器,電視機調用 wait(),A交出遙控器(釋放鎖),JVM 調度遙控器給 B 或 C。B 調用 notify() 歸還遙控器給等待隊列中的線程

四、代碼對比演示

sleep() 不釋放鎖驗證

public class SleepHoldLock {
    static final Object lock = new Object();
    
    public static void main(String[] args) {
        new Thread(() -> {
            synchronized (lock) {
                System.out.println("Thread-A 獲取鎖,睡眠 3 秒");
                try { Thread.sleep(3000); } catch (InterruptedException e) {}
                System.out.println("Thread-A 醒來釋放鎖");
            }
        }, "Thread-A").start();
        
        // 稍微延遲啟動 B,確保 A 先獲取鎖
        new Thread(() -> {
            try { Thread.sleep(100); } catch (InterruptedException e) {}
            synchronized (lock) {
                System.out.println("Thread-B 獲取鎖"); // 3 秒後才會打印
            }
        }, "Thread-B").start();
    }
}

wait() 釋放鎖驗證

public class WaitReleaseLock {
    static final Object lock = new Object();
    
    public static void main(String[] args) {
        new Thread(() -> {
            synchronized (lock) {
                System.out.println("Thread-A 獲取鎖,調用 wait()");
                try { lock.wait(3000); } catch (InterruptedException e) {}
                System.out.println("Thread-A 被喚醒/超時");
            }
        }, "Thread-A").start();
        
        new Thread(() -> {
            try { Thread.sleep(100); } catch (InterruptedException e) {}
            synchronized (lock) {
                System.out.println("Thread-B 獲取鎖"); // 立即打印!A 已釋放鎖
                lock.notify(); // 喚醒 A
            }
        }, "Thread-B").start();
    }
}

五、面試高頻追問

  1. 為什麼 wait() 必須在 synchronized 中?
    因為 wait() 需要釋放監視器,只有擁有監視器的線程才能釋放。JVM 強制檢查當前線程是否為鎖擁有者。
  2. sleep(0) 有什麼用?
    觸發線程重新調度,讓出 CPU 時間片,類似 Thread.yield() 但更強制。
  3. notify() vs notifyAll()?
    notify() 隨機喚醒一個等待線程(可能喚醒同類線程導致「喚醒失效」),notifyAll() 喚醒所有,由 JVM 重新競爭鎖,更安全。
  4. 能否用 sleep 實現生產者-消費者?
    不能。sleep 不釋放鎖,會導致死鎖或忙等待,必須用 wait/notify 或 java.util.concurrent 組件。
  5. InterruptedException 處理最佳實踐?
    捕獲後 Thread.currentThread().interrupt() 恢復中斷狀態,或拋出運行時異常向上傳播,切勿吞掉異常。

六、現代替代方案(Java 5+)

實際開發強烈建議使用 java.util.concurrent 高級組件:

  • Lock/Condition:ReentrantLock + newCondition() 替代 synchronized/wait/notify,支持多條件隊列、可中斷鎖、公平鎖
  • BlockingQueue:ArrayBlockingQueue 直接實現生產者-消費者,內部封裝鎖與等待
  • CountDownLatch/CyclicBarrier/Phaser:線程協作同步工具
  • CompletableFuture:異步編排替代手動線程管理

總結:sleep() 是線程「自我管理」(不釋放鎖),wait() 是線程「協作交互」(釋放鎖)。理解監視器(鎖)所有權轉移是掌握多線程協作的鑰匙。實戰中優先用 JUC 組件,底層原理必須精通。

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