Java多線(xiàn)程中的wait與notify方法詳解
前言
我們知道,線(xiàn)程的調(diào)度是無(wú)序的,但有些情況要求線(xiàn)程的執(zhí)行是有序的。
因此,我們可以使用 wait() 方法來(lái)使線(xiàn)程執(zhí)行有序。
本期講解 Java 多線(xiàn)程中 synchronized 鎖配套使用的 wait 方法、notify方法和notifyAll方法,以及 wait 方法與 sleep 方法之間的區(qū)別、為什么要使用 wait 和 notify 方法。
為什么要使用wait()方法和notify()方法?
當(dāng)我們的 Java 代碼使用 synchronized 進(jìn)行加鎖時(shí),會(huì)出現(xiàn)線(xiàn)程之間搶占資源的情況。
這樣就會(huì)導(dǎo)致某一個(gè)線(xiàn)程不符合條件卻反復(fù)搶到資源,其他線(xiàn)程參與不了資源。
因此得使用 wait() 方法與 notify() 方法來(lái)解決該問(wèn)題。
通過(guò)現(xiàn)實(shí)生活中的經(jīng)歷舉一例子:
把三個(gè)線(xiàn)程比做人,把一臺(tái) ATM 機(jī)比作鎖(synchronized)。
當(dāng)這三個(gè)線(xiàn)程去取錢(qián)時(shí),線(xiàn)程1優(yōu)先進(jìn)入了 ATM 機(jī)里面取錢(qián)。
當(dāng) ATM 里面沒(méi)有錢(qián)時(shí),線(xiàn)程1就出了 ATM 機(jī)。但由于線(xiàn)程離開(kāi)了 ATM 機(jī)后,會(huì)一直與線(xiàn)程2和線(xiàn)程3搶占 ATM 機(jī),因此會(huì)造成一個(gè)極端的后果,就是線(xiàn)程1一直進(jìn)入 ATM 機(jī)然后出 ATM 機(jī),并且一直循環(huán)下去。

以上例子,線(xiàn)程1發(fā)現(xiàn) ATM 沒(méi)錢(qián)可取,卻還是反復(fù)進(jìn)出 ATM 這樣這樣其他線(xiàn)程就無(wú)法嘗試取錢(qián),對(duì)應(yīng)的就是多線(xiàn)程中的多個(gè)線(xiàn)程競(jìng)爭(zhēng)鎖(synchroized)的情況,如何解決以上問(wèn)題。
使用 wait 方法和 notify 方法。當(dāng) ATM(synchronized) 內(nèi)使用了 wait 方法,線(xiàn)程1取不了錢(qián)就會(huì)取消鎖狀態(tài)并且處于等待狀態(tài),當(dāng)其他線(xiàn)程進(jìn)入 ATM 機(jī)并且取到了錢(qián)這時(shí)候就可以使用 notify 方法喚醒 線(xiàn)程1的等待狀態(tài),那么線(xiàn)程1又可以進(jìn)行取錢(qián)操作,也就是進(jìn)行鎖的競(jìng)爭(zhēng)。
在使用 wait 方法后,線(xiàn)程1發(fā)現(xiàn) ATM 里面沒(méi)有錢(qián)可取,就會(huì)通過(guò) wait 方法來(lái)釋放鎖并且進(jìn)行阻塞等待(也就是暫時(shí)不參與 CPU 的調(diào)度、鎖的競(jìng)爭(zhēng)),這個(gè)時(shí)候線(xiàn)程2和線(xiàn)程3就能很好的參與取錢(qián)這個(gè)操作了。
當(dāng)其他線(xiàn)程 使用 notify 方法時(shí),發(fā)現(xiàn) ATM 里面又有錢(qián)可取了。因此就會(huì)喚醒線(xiàn)程1的阻塞等待,這時(shí)線(xiàn)程1又可以參與 ATM(鎖) 的競(jìng)爭(zhēng)。直到,所有的線(xiàn)程都取到錢(qián)為止。
那么使得上述三個(gè)線(xiàn)程能供協(xié)調(diào)的完成取錢(qián)這個(gè)工作,會(huì)用到三個(gè)方法:
- wait() 方法/帶參數(shù)的wait()方法 - 讓當(dāng)前線(xiàn)程進(jìn)入等待阻塞狀態(tài)
- notify() 方法 / notifyAll() 方法 - 喚醒當(dāng)前對(duì)象上等待的線(xiàn)程
注意:wait,notify、notifyAll都是 Object 類(lèi)中的方法。
1. wait()方法
wait 方法使用后:會(huì)把當(dāng)前的執(zhí)行的線(xiàn)程進(jìn)行等待阻塞,然后釋放當(dāng)前線(xiàn)程的鎖狀態(tài),當(dāng)滿(mǎn)足了一定條件后就被喚醒,重新嘗試獲取這個(gè)鎖。
wait 結(jié)束條件的為:
- 其他線(xiàn)程調(diào)用該對(duì)象的 notify 方法,
- wait 等待時(shí)間超時(shí)(wait 方法提供了一個(gè)帶有參數(shù)的版本,可以指定等待時(shí)間)
- 其他線(xiàn)程調(diào)用該等待的線(xiàn)程的 interrupt 方法,導(dǎo)致 wait 拋出 InterruptedException 異常。
解釋?zhuān)篿nterrupt(),在一個(gè)線(xiàn)程中調(diào)用另一個(gè)線(xiàn)程的interrupt()方法,即會(huì)向那個(gè)線(xiàn)程發(fā)出信號(hào)—線(xiàn)程中斷狀態(tài)已被設(shè)置。至于那個(gè)線(xiàn)程何去何從,由具體的代碼實(shí)現(xiàn)決定。
wait 和 notify 方法是 Object 類(lèi)里面的方法,只要是一個(gè)類(lèi)對(duì)象都能調(diào)用這兩個(gè)方法。因此,我們可以寫(xiě)出以下代碼:
public static void main(String[] args) throws InterruptedException {
Object object = new Object();
System.out.println("Hello object");
object.wait();
System.out.println("object結(jié)束");
}運(yùn)行后打印:

以上代碼運(yùn)行后打印出一個(gè)非法的警告:非法的鎖狀態(tài)異常,因?yàn)?wait 方法必須要搭配 synchronized 來(lái)使用,脫離了 synchronized 的前提下 使用 wait 就會(huì)出現(xiàn)報(bào)錯(cuò)。
2. notify()方法
notify 方法是喚醒等待的線(xiàn)程,也就是喚醒調(diào)用了 wait 方法的線(xiàn)程。
notify 方法作用:
- notify 方法也要在同步方法或同步塊中調(diào)用,該方法是用來(lái)通知那些可能等待該對(duì)象的對(duì)象鎖的
- 其它線(xiàn)程,對(duì)其發(fā)出通知notify,并使它們重新獲取該對(duì)象的對(duì)象鎖
- 如有多個(gè)線(xiàn)程處于等待,則線(xiàn)程調(diào)度器會(huì)隨機(jī)挑選一個(gè)調(diào)用 wait 狀態(tài)的線(xiàn)程。
- 在調(diào)用 notify 方法后,當(dāng)前線(xiàn)程不會(huì)立馬釋放該對(duì)象的鎖,要等當(dāng)前調(diào)用 notify 方法的線(xiàn)程執(zhí)行完畢后,才會(huì)釋放該對(duì)象的鎖。
在理解 wait 方法和 notify 方法的作用以及使用方法后,下面我們來(lái)看下 wait 方法和 notify 方法的結(jié)合使用。
3. wait()和notify()方法的使用
代碼案例:使用 notify() 方法喚醒 thread1線(xiàn)程。
- 實(shí)例化一個(gè) Object 類(lèi)的對(duì)象,調(diào)用 wait 和 notify 方法都是用該對(duì)象的引用 object 來(lái)調(diào)用。
- 創(chuàng)建兩個(gè)線(xiàn)程:線(xiàn)程1和線(xiàn)程2,線(xiàn)程1執(zhí)行兩條語(yǔ)句,線(xiàn)程2也執(zhí)行兩條語(yǔ)句。
- 線(xiàn)程1內(nèi)使用 object 來(lái)調(diào)用 wait 方法(兩條語(yǔ)句中間調(diào)用)
- 線(xiàn)程2內(nèi)使用 object 來(lái)調(diào)用 notify 方法(兩條語(yǔ)句中間調(diào)用)
因此,有以下代碼:
public static void main(String[] args) {
Object object = new Object();
Thread thread1 = new Thread(()-> {
synchronized (object) {
System.out.println("thread1開(kāi)始");
try {
object.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("thread1結(jié)束");
}
});
thread1.start();//啟動(dòng)thread1線(xiàn)程
Thread thread2 = new Thread(()-> {
synchronized (object) {
System.out.println("thread2開(kāi)始");
object.notify();
System.out.println("thread2結(jié)束");
}
});
thread2.start();//啟動(dòng)thread2線(xiàn)程
}運(yùn)行后打印:

以上代碼,輸出順序與需求有所差異,但最終還是達(dá)到了效果。
造成輸出順序的不規(guī)則原因?yàn)椋?/p>
當(dāng) thread1 線(xiàn)程被 wait 前打印了語(yǔ)句“thread1開(kāi)始”,thread2 線(xiàn)程 中調(diào)用了 notify 方法,這時(shí)會(huì)喚醒 thread1 線(xiàn)程,但是前提得執(zhí)行完 thread2 中的內(nèi)容“thread2開(kāi)始”、“thread2結(jié)束”這兩個(gè)條語(yǔ)句。隨后才輸出被喚醒的 thread1 線(xiàn)程中的“thread1結(jié)束”語(yǔ)句。
當(dāng)然,既然這樣為啥我們不使用 join() 方法呢,thread1 線(xiàn)程完全執(zhí)行完畢,再執(zhí)行 thread2線(xiàn)程呢?具體情況具體分析,當(dāng)我們的代碼需求滿(mǎn)足使用 join() 方法時(shí),我們就使用 join() 方法。
對(duì)應(yīng)上述代碼,join() 方法會(huì)使 thread1 線(xiàn)程執(zhí)行完畢后再執(zhí)行 thread2 線(xiàn)程。而 wait() 和 notify() 方法會(huì)使 thread1 線(xiàn)程執(zhí)行一部分后,執(zhí)行 thread2 線(xiàn)程,執(zhí)行完 thread2 一部分代碼后,再執(zhí)行thread1 線(xiàn)程。這樣就滿(mǎn)足了特定的條件,類(lèi)似于上文中線(xiàn)程取錢(qián)情況。大家可以自行嘗試一番。
注意,wait() 方法的初心就是為了等待、阻塞的效果。在 synchronized 內(nèi)調(diào)用 wait() 方法,得按 Alt+Enter 這兩個(gè)組合鍵來(lái) try/catch 異常。
4. notifyAll()方法
notifyAll() 方法是用來(lái)喚醒當(dāng)前對(duì)象的所有調(diào)用 wait() 的線(xiàn)程。案例:
- 有三個(gè)線(xiàn)程,線(xiàn)程1為thread1、線(xiàn)程2為thread2、線(xiàn)程3為thread3
- thread1 中輸出兩條語(yǔ)句“thread1開(kāi)始”、“thread1結(jié)束”
- thread2 中輸出兩條語(yǔ)句“thread2開(kāi)始”、“thread2結(jié)束”
- thread1 和 threa2 在兩條語(yǔ)句中間通過(guò) Object 類(lèi)的引用調(diào)用 wait() 方法造成阻塞
- thread3 線(xiàn)程通過(guò) Object 類(lèi)的引用調(diào)用 notifyAll() 方法喚醒所有的阻塞
因此,前兩個(gè)線(xiàn)程都通過(guò) Object 類(lèi)的引用調(diào)用了 wait() 方法造成阻塞,最后一個(gè)線(xiàn)程調(diào)用 notifyAll() 則喚醒了所有調(diào)用 wait() 方法的線(xiàn)程,如以下代碼:
public static void main(String[] args) {
Object object = new Object();//實(shí)例化一個(gè)Object類(lèi)的對(duì)象
Thread thread1 = new Thread(()->{
synchronized (object) {
System.out.println("thread1-開(kāi)始");
try {
object.wait();//thread1中調(diào)用wait方法
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("thread1-結(jié)束");
}
});//創(chuàng)建thread1線(xiàn)程
thread1.start();//啟動(dòng)thread1線(xiàn)程
Thread thread2 = new Thread(()->{
synchronized(object) {
System.out.println("thread2-開(kāi)始");
try {
object.wait();//thread2調(diào)用wait方法
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("thread2-結(jié)束");
}
});//創(chuàng)建thread2線(xiàn)程
thread2.start();//啟動(dòng)thread2線(xiàn)程
Thread thread3 = new Thread(()->{
synchronized (object) {
object.notifyAll();//thread3中調(diào)用notifyAll方法
System.out.println("thread3調(diào)用了notifyAll方法");
}
});//創(chuàng)建thread3線(xiàn)程
thread3.start();//啟動(dòng)thread3線(xiàn)程
}運(yùn)行后打印:

以上代碼,通過(guò) notifyAll() 方法喚醒了所有等待的線(xiàn)程。如果我把 notifyAll() 方法替換為 notify() 方法,此時(shí)就會(huì)隨機(jī)喚醒一個(gè)正在等待的線(xiàn)程。如以下打印結(jié)果:

通過(guò)上面截圖,我們可以觀(guān)察到隨機(jī)喚醒的是 thread1 線(xiàn)程。
5. wait()和sleep()的區(qū)別
wait 與 sleep 之間的區(qū)別:
- wait() 方法是 Object 類(lèi)底下的方法,sleep() 方法是 Thread 類(lèi)底下的靜態(tài)方法。
- wait()方法是搭配 synchronized 來(lái)使用的,而 sleep() 則不需要。
- 核心區(qū)別,初心不同,wait() 方法是為了避免線(xiàn)程之前的搶占資源(解決線(xiàn)程之間的順序控制),而 sleep() 方法是為了讓線(xiàn)程休眠特定的時(shí)間。
- wait() 方法有一個(gè)帶參數(shù)的寫(xiě)法是用來(lái)體現(xiàn)超時(shí)的提醒(避免死等),因此用起來(lái)就感覺(jué)和 sleep() 方法一樣。
案例:
有兩線(xiàn)程,main 線(xiàn)程與 thread 線(xiàn)程,main 線(xiàn)程內(nèi)包含 thread 線(xiàn)程,main 線(xiàn)程內(nèi)有“Hello main”語(yǔ)句, thread 線(xiàn)程內(nèi)有“Hello thread”語(yǔ)句。
在 main 線(xiàn)程內(nèi)創(chuàng)建一個(gè) thread 線(xiàn)程,并且在 thread 線(xiàn)程內(nèi)使用 Object 類(lèi)對(duì)象調(diào)用帶參的 wait() 方法,并設(shè)置參數(shù) 為2000。
在main 方法內(nèi)使用 Object 類(lèi)對(duì)象調(diào)用 notify() 喚醒 thread 線(xiàn)程。使得輸出 main 線(xiàn)程內(nèi)語(yǔ)句后停頓兩秒輸出 thread 線(xiàn)程內(nèi)語(yǔ)句。
有以下代碼:
public static void main(String[] args) {
Object object = new Object();//實(shí)例化一個(gè)Object類(lèi)對(duì)象
Thread thread = new Thread(()->{
synchronized (object) {
try {
object.wait(2000);//thread調(diào)用了帶參wait方法,停頓了兩秒
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Hello thread");
}
});//創(chuàng)建thread線(xiàn)程
thread.start();//啟動(dòng)thread線(xiàn)程
synchronized (object) {
object.notify();//main方法內(nèi)調(diào)用notify方法
}
System.out.println("Hello main");
}運(yùn)行后打印:

輸出“Hello main”語(yǔ)句后停頓了兩秒,輸出“Hello thread”線(xiàn)程。
重點(diǎn):
- wait、notify、notifyAll都是 Object 類(lèi)的方法
- wait、notify、notifyAll 必須搭配 synchronized 關(guān)鍵字來(lái)使用
- 不帶參數(shù)的 wait 方法會(huì)造成死等、帶參數(shù)的 wait 方法則不會(huì)
- wait 方法的初心就是為了線(xiàn)程處于等待、阻塞狀態(tài)
- notify 方法的初心就是為了喚醒同一對(duì)象調(diào)用 wait 方法的隨機(jī)一個(gè)線(xiàn)程
- notifyAll 方法的初心就是為了喚醒同一對(duì)象調(diào)用 wait 方法的所有線(xiàn)程
到此這篇關(guān)于Java多線(xiàn)程中的wait與notify方法詳解的文章就介紹到這了,更多相關(guān)Java的wait與notify內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java Chassis3過(guò)載狀態(tài)下的快速失敗解決分析
本文解密了Java Chassis 3快速失敗相關(guān)的機(jī)制和背后故事,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2024-01-01
Java獲取當(dāng)前操作系統(tǒng)的信息實(shí)例代碼
這篇文章主要介紹了Java獲取當(dāng)前操作系統(tǒng)的信息實(shí)例代碼,具有一定借鑒價(jià)值,需要的朋友可以參考下。2017-12-12
解決springboot responseentity<string>亂碼問(wèn)題
這篇文章主要介紹了解決springboot responseentity<string>亂碼問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-07-07
深入分析JAVA Synchronized關(guān)鍵字
這篇文章主要介紹了析JAVA Synchronized關(guān)鍵字的相關(guān)知識(shí),文中代碼非常詳細(xì),幫助大家更好的理解和學(xué)習(xí),感興趣的朋友可以了解下2020-06-06
httpclient模擬post請(qǐng)求json封裝表單數(shù)據(jù)的實(shí)現(xiàn)方法
下面小編就為大家?guī)?lái)一篇httpclient模擬post請(qǐng)求json封裝表單數(shù)據(jù)的實(shí)現(xiàn)方法。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2016-12-12
解讀@NoArgsConstructor,@AllArgsConstructor,@RequiredArgsConstr
這篇文章主要介紹了解讀@NoArgsConstructor,@AllArgsConstructor,@RequiredArgsConstructor的區(qū)別及在springboot常用地方,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-12-12
spring boot中xalan引入報(bào)錯(cuò)系統(tǒng)找不到指定的文件原因分析
這篇文章主要介紹了spring boot中xalan引入報(bào)錯(cuò)系統(tǒng)找不到指定的文件,主要原因是內(nèi)嵌的tomcat9.0.36,本文給大家分享最新解決方法,需要的朋友可以參考下2023-08-08
Java使用Optional實(shí)現(xiàn)優(yōu)雅避免空指針異常
空指針異常(NullPointerException)可以說(shuō)是Java程序員最容易遇到的問(wèn)題了。為了解決這個(gè)問(wèn)題,Java?8?版本中推出了?Optional?類(lèi),本文就來(lái)講講如何使用Optional實(shí)現(xiàn)優(yōu)雅避免空指針異常吧2023-03-03

