在Java多線程編程中,線程間的通信與協作是實現復雜并發邏輯的關鍵。wait()
、notify()
以及sleep()
方法作為線程控制的重要工具,有著各自獨特的使用場景與規則。本文將深入探討wait()
和notify()
的協作機制,以及sleep()
的阻塞特性,同時重點解析wait()
必須在循環中調用的核心原因——防止虛假喚醒(Spurious Wakeup)。
一、wait/notify:線程間通信的橋梁
1.1 wait/notify的核心功能
wait()
和notify()
是Object類的方法,主要用于線程間的協作與通信。
wait()
:當一個線程調用wait()
方法時,它會釋放當前持有的對象鎖,并進入等待狀態。直到其他線程調用同一個對象的notify()
或notifyAll()
方法將其喚醒。notify()
:隨機喚醒一個在該對象上等待的線程;而notifyAll()
則會喚醒所有在該對象上等待的線程。
關鍵操作時序圖
1.2 wait/notify的使用規則
wait()
和notify()
必須在synchronized
同步代碼塊或同步方法中使用,這是因為它們的操作依賴于對象鎖的狀態。若不在同步環境中調用,會拋出IllegalMonitorStateException
異常。
1.3 典型使用場景:生產者-消費者模型
class SharedResource {private int data;private boolean isEmpty = true;public synchronized void produce(int value) {while (!isEmpty) {try {wait();} catch (InterruptedException e) {e.printStackTrace();}}data = value;isEmpty = false;notifyAll();}public synchronized void consume() {while (isEmpty) {try {wait();} catch (InterruptedException e) {e.printStackTrace();}}System.out.println("Consumed: " + data);isEmpty = true;notifyAll();}
}
在上述代碼中,生產者線程在緩沖區滿時調用wait()
等待,消費者線程消費數據后通過notifyAll()
喚醒生產者;反之亦然。
二、sleep:簡單的線程阻塞
2.1 sleep的特性
sleep()
是Thread類的靜態方法,用于讓當前線程暫停執行指定的時間(以毫秒為單位)。與wait()
不同,sleep()
不會釋放線程持有的鎖。
2.2 使用場景
- 模擬延遲:在測試代碼或需要控制執行節奏的場景中,通過
sleep()
讓線程暫停一段時間。 - 避免過度占用資源:例如在輪詢檢查某個條件時,加入
sleep()
可以減少CPU的占用。
public class SleepExample {public static void main(String[] args) {Thread thread = new Thread(() -> {synchronized (SleepExample.class) {try {System.out.println("線程開始睡眠");Thread.sleep(2000);System.out.println("線程睡眠結束");} catch (InterruptedException e) {e.printStackTrace();}}});thread.start();}
}
在上述代碼中,線程在持有鎖的情況下進行睡眠,期間其他線程無法獲取該鎖。
三、為什么wait()必須在循環中調用:防止虛假喚醒
3.1 什么是虛假喚醒
虛假喚醒(Spurious Wakeup)是指線程在沒有被其他線程調用notify()
或notifyAll()
的情況下被喚醒。雖然這種情況在實際中并不常見,但Java的wait()
機制允許它發生,這是JVM底層實現的結果。
3.2 循環檢查的必要性
如果不使用循環,當發生虛假喚醒時,線程會誤以為等待的條件已經滿足,繼續執行后續代碼,可能導致程序邏輯錯誤。因此,wait()
必須配合while
循環使用,每次被喚醒后重新檢查條件是否真正滿足。
synchronized (obj) {while (!condition) {obj.wait();}// 條件滿足,執行相應邏輯
}
通過while
循環,即使發生虛假喚醒,線程也會重新進入等待狀態,直到條件真正滿足才繼續執行。
四、總結
wait/notify
:用于線程間的通信與協作,必須在同步環境中使用,且wait()
要配合while
循環防止虛假喚醒。sleep
:單純的線程阻塞,不釋放鎖,適用于控制線程執行節奏。
理解這些方法的特性與使用規則,是編寫正確、高效的多線程程序的基礎。在實際開發中,合理運用這些工具,可以實現復雜的線程協作邏輯,同時避免因不當使用導致的各種問題。