Java通過notify和wait實現(xiàn)線程間的通信功能
一、前言
在軟件開發(fā)中,線程是實現(xiàn)并發(fā)執(zhí)行的重要手段,然而,線程之間的協(xié)作與通信卻是開發(fā)者必須重點考慮的挑戰(zhàn)之一。Java作為一種廣泛應用于多線程編程的語言,提供了一套強大而靈活的機制,讓不同線程之間能夠優(yōu)雅地交替執(zhí)行、傳遞信息,以實現(xiàn)協(xié)調(diào)合作。
本文將深入探討Java中通過notify和wait實現(xiàn)線程間通信的機制。
二、notify 和 wait
2.1 wait
2.1.1 wait 基本介紹
通過源碼我們可以知道 wait 是 object 對象方法,用于實現(xiàn)線程間的等待和通知機制。當一個線程調(diào)用wait()方法時,它會釋放當前所持有的對象鎖,并進入等待狀態(tài),直到被其他線程調(diào)用相同對象上的notify()或notifyAll()方法喚醒。
public final void wait() throws InterruptedException {
wait(0);
}
2.1.2 wait 注意點
- 要想使用wait方法,當前線程必須擁有對應 object 的 mointor,即必須要擁有對應對象的鎖,否則會報
java.lang.IllegalMonitorStateException比如:
Object o = new Object(); o.wait(); //java.lang.IllegalMonitorStateException
- 當調(diào)用
wait()方法的線程被另一個線程中斷時,wait()方法會拋出InterruptedException異常。 - 線程也可能會在沒有收到通知、中斷或超時的情況下被喚醒,即所謂的虛假喚醒。雖然這種情況在實踐中很少發(fā)生,但應用程序必須通過測試應該導致線程被喚醒的條件來防范這種情況,如果條件不滿足,則繼續(xù)等待。換句話說,等待應該總是在循環(huán)中發(fā)生,如下所示:
synchronized (obj) {
while (<condition does not hold>)
obj.wait(timeout);
... // Perform action appropriate to condition
}
2.1.3 wait 使用場景
wait()方法是在Java中用于線程間通信和同步的重要方法之一。它通常與synchronized關鍵字一起使用,用于在多線程中協(xié)調(diào)線程之間的操作。下面是wait()方法的一些常見使用場景:
- 協(xié)調(diào)多個線程的操作:
wait()方法通常與notify()和notifyAll()方法一起使用,用于在多線程之間協(xié)調(diào)操作。一個線程可以調(diào)用wait()進入等待狀態(tài),等待其他線程調(diào)用相同對象的notify()或notifyAll()方法來喚醒它。 - 等待條件滿足:線程可以調(diào)用
wait()方法等待某個條件的滿足。例如,一個線程可能等待某個變量的值發(fā)生改變或者等待某個事件發(fā)生。 - 線程安全的隊列實現(xiàn):
wait()方法常用于實現(xiàn)線程安全的隊列。當隊列為空時,消費者線程調(diào)用wait()等待生產(chǎn)者線程向隊列中添加元素;當隊列已滿時,生產(chǎn)者線程調(diào)用wait()等待消費者線程從隊列中取走元素。 - 實現(xiàn)生產(chǎn)者-消費者模型:
wait()方法在生產(chǎn)者-消費者模型中起著重要作用。生產(chǎn)者向共享的緩沖區(qū)添加數(shù)據(jù)時,如果緩沖區(qū)已滿,生產(chǎn)者線程調(diào)用wait()等待消費者取走數(shù)據(jù);消費者從緩沖區(qū)取數(shù)據(jù)時,如果緩沖區(qū)為空,消費者線程調(diào)用wait()等待生產(chǎn)者添加數(shù)據(jù)。 - 線程間通信:
wait()方法是線程間通信的重要手段之一。線程可以通過等待并喚醒的機制來實現(xiàn)信息的傳遞和協(xié)調(diào)。
2.1.4 wait 的執(zhí)行原理
- 當線程調(diào)用
wait()方法時,它會釋放當前持有的對象鎖,使得其他線程可以訪問這個對象并執(zhí)行同步代碼塊。 - 調(diào)用
wait()方法的線程會進入對象的等待隊列,等待其他線程調(diào)用相同對象的notify()或notifyAll()方法來喚醒它。 - 當另一個線程調(diào)用相同對象的
notify()方法或notifyAll()方法時,等待隊列中的線程會被喚醒,然后競爭對象的鎖。 - 喚醒的線程會嘗試重新獲取對象鎖,然后繼續(xù)執(zhí)行。
2.1.5 wait 使用
- wait()或者wait(0): 調(diào)用該方法的線程進入WAITING狀態(tài),只有等待另外線程的通知或被中斷才會返回,需要注意調(diào)用 wait() 方法后,會釋放對象的鎖
- wait(long) 會釋放鎖,超時等待一段時間,這里的參數(shù)時間是毫秒,也就是等待長達毫秒,如果沒有通知就超時返回
- wait(long,int) 會釋放鎖,該方法與 wait(long) 類似,多了一個 nanos 參數(shù),這個參數(shù)表示額外時間(以納秒為單位,范圍是 0-999999), 所以超時的時間還需要加上 nanos 納秒。
2.2 notify
首先我們需要知道 nofity 和 notifyall 基本上是等價的,如沒有特別標明,nofity 和 nofityall 是一樣的
2.2.1 notify 基本介紹
通過源碼我們可以知道 notify 是 object 對象方法。
notify()是線程間通信的一種機制,用于喚醒在當前對象上等待的一個線程。當一個線程調(diào)用notify()方法時,它會喚醒正在該對象上等待的單個線程(如果有多個線程在等待,系統(tǒng)無法確定哪個線程會被喚醒,因為選擇是隨機的)。這個被喚醒的線程將從等待狀態(tài)變?yōu)榭蛇\行狀態(tài),但并不意味著立即獲得對象的鎖。
public final native void notify();
當一個線程調(diào)用notifyAll()時,它會喚醒在當前對象上等待的所有線程,使它們從等待狀態(tài)轉變?yōu)榭蛇\行狀態(tài)。這樣,所有等待中的線程都有機會爭取獲取對象鎖,在某個線程獲得鎖后,它們會競爭執(zhí)行。
2.2.2 notify 注意點
下面是關于notify()方法的一些重要點:
- 使用條件變量:
notify()方法通常與條件變量一起使用,用于線程間的協(xié)作。一個線程等待某個條件變?yōu)檎妫硪粋€線程在某種情況下會改變條件,并調(diào)用notify()來通知等待的線程。 - notifyAll()方法:與
notify()不同的是,notifyAll()方法喚醒在當前對象上等待的所有線程,而不僅僅是一個線程。這樣做可以避免遺漏任何等待中的線程,但同一時間獲取鎖的只會有一個線程。 - Object類中的方法:
notify()方法是Object類的一個方法,因此任何Java對象都可以調(diào)用notify()和wait()方法來進行線程間的通信。 - 必須在同步塊中調(diào)用:為了調(diào)用
notify()方法,必須在同步塊(synchronized塊)中對包含該對象的鎖進行操作。 - 隨機性:當多個線程在同一個對象上等待時,調(diào)用
notify()會隨機選擇一個線程喚醒,因此不能確定哪個線程會被喚醒。 - 喚醒等待線程:被喚醒的線程會嘗試重新獲取對象鎖,一旦獲得鎖,它會從
wait()方法返回,并繼續(xù)執(zhí)行后續(xù)代碼。
2.2.3 nofityall
以下是關于notifyAll()方法的詳細介紹:
- 喚醒所有等待線程:
notifyAll()方法被調(diào)用時,會喚醒在當前對象上等待的所有線程,使它們從阻塞狀態(tài)轉變?yōu)榫途w狀態(tài)。這樣,所有等待中的線程都有機會去競爭獲取對象的鎖。 - 解決等待問題:
notifyAll()通常用于解決多線程之間的通信和協(xié)調(diào)問題。當某個條件得到滿足時,調(diào)用notifyAll()可以通知所有等待該條件的線程。 - 謹慎使用:需要謹慎使用
notifyAll(),因為喚醒所有線程可能會導致競爭和性能問題。在某些情況下,如果只有一個線程是合適的接收方,那么使用notify()來喚醒特定線程會更有效率。 - 必須在同步塊中調(diào)用:就像
notify()方法一樣,notifyAll()也必須在同步塊(synchronized塊)內(nèi)調(diào)用,以確保在對象上同步。通過同步塊,確保在調(diào)用notifyAll()時,持有對象的鎖。 - 競爭獲取鎖:被喚醒的線程會嘗試獲取對象的鎖,一旦獲得鎖,它們將從
wait()方法返回,并開始競爭執(zhí)行。 - 釋放鎖:調(diào)用
notifyAll()并不會釋放對象鎖,它僅喚醒等待的線程,這些線程會在合適的時機爭奪鎖。
2.2.4 notify 使用場景
notify()的使用場景:
- 單個喚醒:當只需喚醒等待隊列中的一個線程時,適合使用
notify()。這種情況下,最多只有一個等待線程被喚醒,選擇被喚醒的線程是不確定的。 - 資源更新:當某個共享資源的狀態(tài)發(fā)生變化時,并且只需要通知一個線程來處理這種變化時,可以使用
notify()。
notifyAll()的使用場景:
- 多個喚醒:當需要喚醒所有等待線程來處理某個共享資源的變化時,應該使用
notifyAll()。這種情況下,所有等待線程都會被喚醒。 - 條件變更:當共享資源的狀態(tài)發(fā)生變化對多個線程都有影響時,可以使用
notifyAll()來通知所有等待線程,因為多個線程可能對資源的狀態(tài)變化做出不同的響應。 - 避免競爭問題:在某些情況下,使用
notifyAll()可以避免因為只喚醒一個線程而導致的競爭問題,確保所有等待線程都能及時做出響應。
2.2.5 notify 的執(zhí)行原理
notify()方法的執(zhí)行原理:
- 當一個線程調(diào)用對象的
notify()方法時,這個對象的監(jiān)視器被激活。 - 在等待該對象的線程中,會有一個線程從等待隊列中被選中,進入到鎖定狀態(tài),并嘗試重新獲取對象的鎖。
- 一旦獲取到鎖,該線程會從
wait()方法返回,并繼續(xù)執(zhí)行。 - 其他等待該對象的線程仍然處于等待狀態(tài),需要重新競爭獲取對象的鎖來繼續(xù)執(zhí)行。
notifyAll()方法的執(zhí)行原理:
- 當一個線程調(diào)用對象的
notifyAll()方法時,所有等待該對象的線程將被喚醒。 - 被喚醒的線程會一起競爭對象的鎖,以便能夠繼續(xù)執(zhí)行。
- 每個被喚醒的線程嘗試重新獲取鎖,一旦成功獲取到鎖,它們會從
wait()方法返回,并接著執(zhí)行后續(xù)代碼。
2.2.6 notify和notifyAll 注意事項和要點:
- 使用 notify和notifyAll 時需要先對調(diào)用的對象加鎖,否則會報錯
notify()方法和notifyAll()方法必須在同步塊中(synchronized塊)調(diào)用,以確保在調(diào)用這些方法時對象的鎖處于正確狀態(tài)。- 被喚醒的線程在獲取到鎖并從
wait()方法返回后,可能需要檢查等待期間的條件是否發(fā)生了變化,從而決定是否繼續(xù)執(zhí)行。
三、wait/nofity 經(jīng)典使用方式
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.TimeUnit;
public class WaitNotify {
static boolean flag = true;
static Object lock = new Object();
public static void main(String[] args) {
Thread waitThread = new Thread(new Wait(), "waitThread");
waitThread.start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(new Notify(), "notifyThread").start();
}
//wait
static class Wait implements Runnable {
public void run() {
synchronized (lock) {
while (flag) {
try {
System.out.println(Thread.currentThread() + " flag is true" + new SimpleDateFormat("HH:mm:ss").format(new Date()));
//沒有釋放資源 wait 等待
lock.wait();
} catch (InterruptedException e) {
}
}
System.out.println(Thread.currentThread() + " flag is false" + new SimpleDateFormat("HH:mm:ss").format(new Date()));
}
}
}
static class Notify implements Runnable {
public void run() {
synchronized (lock) {
while (flag) {
System.out.println(Thread.currentThread() + "hold lock notify" + new SimpleDateFormat("HH:mm:ss").format(new Date()));
//資源準備
好了,nofity
lock.notifyAll();
flag = false;
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
synchronized (lock) {
System.out.println(Thread.currentThread() + "hold lock again. notify" + new SimpleDateFormat("HH:mm:ss").format(new Date()));
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
這是最經(jīng)典的等待/通知的范式:但資源沒有準備好時,wait 等待,當資源準備好了,notify 通知。
四、總結
文章詳細講解了Java中wait和notify方法的使用方法和注意事項,通過這些方法實現(xiàn)線程間的協(xié)調(diào)與通信,并列舉了常見的使用場景,如協(xié)調(diào)多個線程操作、實現(xiàn)生產(chǎn)者-消費者模型等。此外,文章還強調(diào)了wait和notify方法必須在同步塊中調(diào)用,以確保線程安全。
以上就是Java通過notify和wait實現(xiàn)線程間的通信功能的詳細內(nèi)容,更多關于Java notify wait線程通信的資料請關注腳本之家其它相關文章!
相關文章
Java啟動參數(shù)(-,?-X,?-XX參數(shù))的使用
本文主要介紹了Java啟動參數(shù)(-,?-X,?-XX參數(shù))的使用,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2023-06-06
Java實現(xiàn)貪吃蛇大作戰(zhàn)小游戲的示例代碼
本文主要介紹了Java實現(xiàn)貪吃蛇大作戰(zhàn)小游戲的示例代碼,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2022-07-07
Java中日期的加減&String Date Calendar的相互轉換方式
這篇文章主要介紹了Java中日期的加減&String Date Calendar的相互轉換方式,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2025-05-05
springboot基于keytool實現(xiàn)https的雙向認證示例教程
這篇文章主要介紹了springboot基于keytool實現(xiàn)https的雙向認證,本文通過實例代碼給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2023-06-06
Intellij IDEA使用restclient測試的教程圖解
這篇文章主要介紹了Intellij IDEA使用restclient測試的教程圖解,本文通過圖文并茂的形式給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2021-01-01
Spring?boot?RedisTemplate?序列化服務化配置方式
這篇文章主要介紹了Springboot?RedisTemplate序列化服務化配置方式,本文通過實例代碼給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2023-07-07
SpringCloud Eureka 服務注冊實現(xiàn)過程
這篇文章主要介紹了SpringCloud Eureka 服務注冊實現(xiàn)過程,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下2019-10-10

