JAVA中wait()和notify()如何使用詳解
前言
大家應(yīng)該都知道,線程之間是搶占式隨機(jī)執(zhí)行的,但是我們并不希望這樣。因?yàn)檫@樣非?;靵y,并不好去預(yù)估程序的執(zhí)行結(jié)果。我們甚至希望,線程之間能夠配合執(zhí)行,那么我們就可以使用wait()和notify()來做到。
一、wait()方法
wait有兩個(gè)方法:
wait():讓當(dāng)前線程進(jìn)入等待(阻塞)狀態(tài)。死等,沒有喚醒就會一直阻塞
wait(long timeout) :指定時(shí)間內(nèi),讓線程進(jìn)入等待(阻塞)狀態(tài)。
1.wait()主要做的事
- 使當(dāng)前執(zhí)行代碼的線程進(jìn)行等待。(把線程放到等待隊(duì)列中)
- 釋放當(dāng)前的鎖
- 滿足條件被notify()喚醒,重新嘗試獲取這個(gè)鎖
public static void main(String[] args) throws InterruptedException {
Object object = new Object();
System.out.println("wait 之前");
/**
* Object中有wait方法,通過Object對象調(diào)用wait方法
* wait沒有參數(shù)的版本是默認(rèn)死等
* wait帶參數(shù)的版本是指定超時(shí)時(shí)間,如果超時(shí)了還沒有notify就繼續(xù)執(zhí)行
*
* 使用wait有三個(gè)操作:
* (1)釋放鎖
* (2)進(jìn)入阻塞等待,準(zhǔn)備接受通知
* (3)收到通知之后喚醒,并且重新獲取鎖
*/
/**
* 因?yàn)閣ait會解鎖,所以wait必須在synchronized內(nèi)部
* 而且synchronized括號內(nèi)的Object對象,必須和調(diào)用wait的是同一對象
*/
synchronized(object) {
object.wait();
}
System.out.println("wait 之后");
}
2.wait()的結(jié)束條件
其他線程調(diào)?該對象的 notify ?法.
wait 等待時(shí)間超時(shí) (wait ?法提供?個(gè)帶有 timeout 參數(shù)的版本, 來指定等待時(shí)間).
其他線程調(diào)?該等待線程的 interrupt ?法, 導(dǎo)致 wait 拋出 InterruptedException 異常,并清除中斷標(biāo)志位(給程序員自由發(fā)揮的空間),并重新嘗試獲取鎖。
public static void main(String[] args) throws InterruptedException {
Object locker = new Object();
Thread t1 = new Thread(() -> {
//獲取當(dāng)前線程
Thread current = Thread.currentThread();
int count = 0;
//標(biāo)志位默認(rèn)為false,為true就是被中斷了
while (!current.isInterrupted()) {
//如果中斷位被清空了,不會執(zhí)行第二次
System.out.println("t1線程第一次執(zhí)行" + count++);
synchronized (locker) {
//因?yàn)門hread并沒有處理這個(gè)異常,所以必須在這里使用try-catch處理一下
try {
//開始等待
//如果被interrupt中斷會拋出異常,并清除中斷位
//并重新嘗試獲取鎖
locker.wait();
} catch (InterruptedException e) {
System.err.println("被interrupt喚醒");
}
}
}
});
t1.start();
while (true) {
Thread.sleep(1000);
t1.interrupt();
}
}3.有參數(shù)的wait()
public static void main(String[] args) {
Object locker = new Object();
Thread t1 = new Thread(() -> {
long start = System.currentTimeMillis();
System.out.println("wait之前");
synchronized (locker) {
try {
locker.wait(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
long end = System.currentTimeMillis();
System.out.println("wait之后 耗時(shí):" + (end - start));
});
t1.start();
}結(jié)果:

二、notify()
notify?法是喚醒等待的線程,主要配合wait():
notify():喚醒同一對象調(diào)用正在wait()的線程,如果有多個(gè)線程正在wait()就會隨機(jī)喚醒一個(gè)線程
notifyAll():喚醒所有正在wait()的線程
1.notify()主要做的事
?法notify()也要在同步?法或同步塊中調(diào)?,該?法是?來通知那些可能等待該對象的對象鎖的其它線程,對其發(fā)出通知notify,并使它們重新獲取該對象的對象鎖。
如果有多個(gè)線程等待,則有線程調(diào)度器隨機(jī)挑選出?個(gè)呈 wait 狀態(tài)的線程。(并沒有 "先來后到")
在notify()?法后,當(dāng)前線程不會?上釋放該對象鎖,要等到執(zhí)?notify()?法的線程將程序執(zhí)?完,也就是退出同步代碼塊之后才會釋放對象鎖。
public static void main(String[] args) throws InterruptedException {
Object locker = new Object();
Thread t1 = new Thread(() -> {
synchronized (locker) {
System.out.println("wait之前~");
try {
locker.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("wait之后!");
}
});
Thread t2 = new Thread(() -> {
synchronized (locker) {
System.out.println("notify之前");
locker.notify();
//notify之后,并不會馬上釋放鎖結(jié)束,至少會
//把synchronized中的語句執(zhí)行完
System.out.println("未解鎖之后的notify");
}
//看是否會執(zhí)行到這一條語句
System.err.println("解鎖之后的notify");
});
t1.start();
//防止執(zhí)行到notify了,t2還沒阻塞(wait)
Thread.sleep(500);
t2.start();
}結(jié)果分析:

注意事項(xiàng):

2.notify() 會喚醒sleep()嗎?
public static void main(String[] args) throws InterruptedException {
Object locker = new Object();
Thread t1 = new Thread(() -> {
System.out.println("sleep睡眠");
try {
//sleep不需要在synchronized里
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("sleep睡醒了");
});
Thread t2 = new Thread(() -> {
synchronized (locker) {
System.out.println("notify之前");
locker.notify();
System.out.println("notify之后");
}
});
t1.start();
Thread.sleep(1000);
t2.start();
}結(jié)果:

3.notifyAll()
喚醒所有正在等待的線程
public static void main(String[] args) throws InterruptedException {
Object locker = new Object();
Thread t1 = new Thread(() -> {
System.out.println("t1線程開始");
synchronized (locker) {
try {
locker.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.err.println("t1線程結(jié)束");
});
Thread t2 = new Thread(() -> {
System.out.println("t2線程開始");
synchronized (locker) {
try {
locker.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.err.println("t2線程結(jié)束");
});
Thread t3 = new Thread(() -> {
System.out.println("t3線程開始");
synchronized (locker) {
try {
locker.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.err.println("t3線程結(jié)束");
});
Thread t4 = new Thread(() -> {
synchronized (locker) {
System.out.println("notify之前");
locker.notifyAll();
System.out.println("notify之后");
}
});
t1.start();
t2.start();
t3.start();
//保證上面的線程都已經(jīng)執(zhí)行了wait
Thread.sleep(1000);
t4.start();
}
三、調(diào)用 wait\notify\synchronized 使用的對象
注意:wait和notify都必須放在synchronized中,不然會拋出異常:IllegalMonitorStateException
public static void main(String[] args) {
Object locker = new Object();
Thread t1 = new Thread(() -> {
System.out.println("t1線程開始");
try {
locker.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.err.println("t1線程結(jié)束");
});
Thread t4 = new Thread(() -> {
try {
Thread.sleep(1000);
locker.notify();
} catch (InterruptedException e) {
e.printStackTrace();
}
});
t1.start();
t4.start();
}
并且synchronized括號內(nèi)部,必須和調(diào)用的是同一對象,不然依然會拋異常:
public static void main(String[] args) {
Object locker1 = new Object();
Object locker2 = new Object();
Thread t1 = new Thread(() -> {
synchronized (locker1) {
System.out.println("t1線程開始");
try {
locker2.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.err.println("t1線程結(jié)束");
}
});
Thread t4 = new Thread(() -> {
synchronized (locker1) {
try {
Thread.sleep(1000);
locker2.notify();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
t1.start();
t4.start();
}
四、wait和sleep的比較/區(qū)別(面試題)
其實(shí)理論上 wait 和 sleep 完全是沒有可?性的,因?yàn)?個(gè)是?于線程之間的通信的,?個(gè)是讓線程阻塞?段時(shí)間。
相同點(diǎn):
都會讓線程放棄執(zhí)行一段時(shí)間
都可以被interrupt喚醒,并且都會拋出 InterruptedException 異常,并且清空標(biāo)志位
不同點(diǎn):
wait是Object的方法,sleep是Thread的靜態(tài)方法
wait必須在synchronized中,sleep不需要
wait阻塞的時(shí)候釋放鎖,sleep并不會,sleep會抱著鎖一起阻塞
wait用于線程間通信(如生產(chǎn)者-消費(fèi)者模型),sleep用于阻塞線程
總結(jié)
到此這篇關(guān)于JAVA中wait()和notify()如何使用的文章就介紹到這了,更多相關(guān)Java wait()和notify()內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- java多線程之wait(),notify(),notifyAll()的詳解分析
- Java的wait(), notify()和notifyAll()使用心得
- Java 中Object的wait() notify() notifyAll()方法使用
- Java使用wait() notify()方法操作共享資源詳解
- Java多線程通信wait()和notify()代碼實(shí)例
- java wait()/notify() 實(shí)現(xiàn)生產(chǎn)者消費(fèi)者模式詳解
- Java多線程wait()和notify()方法詳細(xì)圖解
- java使用wait()和notify()線程間通訊的實(shí)現(xiàn)
相關(guān)文章
Mybatis如何傳入多個(gè)參數(shù)的實(shí)現(xiàn)代碼
這篇文章主要介紹了Mybatis如何傳入多個(gè)參數(shù)的實(shí)現(xiàn)代碼,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-12-12
ElasticSearch不停機(jī)重建索引延伸思考及優(yōu)化詳解
這篇文章主要為大家介紹了ElasticSearch不停機(jī)重建索引延伸思考及優(yōu)化詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-02-02
SpringBoot中注解@ConfigurationProperties與@Value的區(qū)別與使用詳解
本文主要介紹了SpringBoot中注解@ConfigurationProperties與@Value的區(qū)別與使用,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-09-09
帶你了解Java數(shù)據(jù)結(jié)構(gòu)和算法之無權(quán)無向圖
這篇文章主要為大家介紹了Java數(shù)據(jù)結(jié)構(gòu)和算法之無權(quán)無向圖?,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下,希望能夠給你帶來幫助2022-01-01
SpringBoot項(xiàng)目運(yùn)行jar包啟動的步驟流程解析
這篇文章主要介紹了SpringBoot項(xiàng)目運(yùn)行jar包啟動的步驟流程,本文分步驟通過圖文并茂的形式給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友參考下吧2020-07-07
Java結(jié)構(gòu)型設(shè)計(jì)模式中建造者模式示例詳解
建造者模式,是一種對象構(gòu)建模式 它可以將復(fù)雜對象的建造過程抽象出來,使這個(gè)抽象過程的不同實(shí)現(xiàn)方法可以構(gòu)造出不同表現(xiàn)的對象。本文將通過示例講解建造者模式,需要的可以參考一下2022-09-09

