圖解 Java多線程中的 wait() 和 notify() 方法
胖虎和朋友原創(chuàng)的視頻教程有興趣的可以看看:
(文末附課程大綱)
一、線程間等待與喚醒機(jī)制
wait()和notify()是Object類的方法,用于線程的等待與喚醒,必須搭配synchronized 鎖來使用。
多線程并發(fā)的場(chǎng)景下,有時(shí)需要某些線程先執(zhí)行,這些線程執(zhí)行結(jié)束后其他線程再繼續(xù)執(zhí)行。
比如:一個(gè)長(zhǎng)跑比賽,裁判員要等跑步運(yùn)動(dòng)員沖線了才能宣判比賽結(jié)束,那裁判員線程就得等待所有的運(yùn)動(dòng)員線程運(yùn)行結(jié)束后,再喚醒這個(gè)裁判線程。
二、等待方法wait()
wait 做的事情:
使當(dāng)前執(zhí)行代碼的線程進(jìn)行等待. (把線程放到等待隊(duì)列中) 釋放當(dāng)前的鎖 滿足一定條件時(shí)被喚醒, 重新嘗試獲取這個(gè)鎖.
wait 要搭配 synchronized 來使用. 脫離 synchronized 使用 wait 會(huì)直接拋出異常.

wait 結(jié)束等待的條件:
其他線程調(diào)用該對(duì)象的 notify 方法. wait 等待時(shí)間超時(shí) (wait 方法提供一個(gè)帶有 timeout 參數(shù)的版本, 來指定等待時(shí)間). 其他線程調(diào)用該等待線程的 interrupted方法, 導(dǎo)致 wait 拋出InterruptedException異常.
注意事項(xiàng):
調(diào)用 wait()方法的前提是首先要獲取該對(duì)象的鎖(synchronize對(duì)象鎖)調(diào)用 wait()方法會(huì)釋放鎖,本線程進(jìn)入等待隊(duì)列等待被喚醒,被喚醒后不是立即恢復(fù)執(zhí)行,而是進(jìn)入阻塞隊(duì)列,競(jìng)爭(zhēng)鎖
等待方法:
1.癡漢方法,死等,線程進(jìn)入阻塞態(tài)(WAITING)直到有其他線程調(diào)用notify方法喚醒

2.等待一段時(shí)間,若在該時(shí)間內(nèi)線程被喚醒,則繼續(xù)執(zhí)行,若超過相應(yīng)時(shí)間還沒有其他線程喚醒此線程,此線程不再等待,恢復(fù)執(zhí)行。

調(diào)用wait方法之后:

三、喚醒方法notify()
notify 方法是喚醒等待的線程.
方法 notify()也要在同步方法或同步塊中調(diào)用,該方法是用來通知那些可能等待該對(duì)象的對(duì)象鎖的其它線程,對(duì)其發(fā)出通知notify,并使它們重新獲取該對(duì)象的對(duì)象鎖。如果有多個(gè)線程等待,則有線程調(diào)度器隨機(jī)挑選出一個(gè)呈 wait狀態(tài)的線程。(并沒有 “先來后到”)在 notify()方法后,當(dāng)前線程不會(huì)馬上釋放該對(duì)象鎖,要等到執(zhí)行notify()方法的線程將程序執(zhí)行完,也就是退出同步代碼塊之后才會(huì)釋放對(duì)象鎖。
注意事項(xiàng):
notify():隨機(jī)喚醒一個(gè)處在等待狀態(tài)的線程。notifyAll():喚醒所有處在等待狀態(tài)的線程。
無論是wait還是notify方法,都需要搭配synchronized鎖來使用(等待和喚醒,也是需要對(duì)象)

四、關(guān)于wait和notify內(nèi)部等待問題(重要)
對(duì)于wait和notify方法,其實(shí)有一個(gè)阻塞隊(duì)列也有一個(gè)等待隊(duì)列。
阻塞隊(duì)列表示同一時(shí)間只有一個(gè)線程能獲取到鎖,其他線程進(jìn)入阻塞隊(duì)列 等待隊(duì)列表示調(diào)用wait (首先此線程要獲取到鎖,進(jìn)入等待隊(duì)列,釋放鎖)
舉個(gè)栗子:
現(xiàn)有如下定義的等待線程任務(wù)
private static class WaitTask implements Runnable {
private Object lock;
public WaitTask(Object lock) {
this.lock = lock;
}
@Override
public void run() {
synchronized (lock) {
System.out.println(Thread.currentThread().getName() + "準(zhǔn)備進(jìn)入等待狀態(tài)");
// 此線程在等待lock對(duì)象的notify方法喚醒
try {
lock.wait();
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(Thread.currentThread().getName() + "等待結(jié)束,本線程繼續(xù)執(zhí)行");
}
}
}
然后創(chuàng)建三個(gè)等待線程:

由于同一時(shí)間只有一個(gè)線程(隨機(jī)調(diào)度)能獲取到synchronized鎖,所以會(huì)有兩個(gè)線程沒競(jìng)爭(zhēng)到鎖,從而進(jìn)入了阻塞隊(duì)列。
這里假如t2先競(jìng)爭(zhēng)到了鎖,所以先會(huì)阻塞t1和t3:

又由于調(diào)用wait方法會(huì)釋放鎖,調(diào)用wait方法的線程t2就會(huì)進(jìn)入等待隊(duì)列,直到被notify喚醒或者超時(shí)自動(dòng)喚醒。

然后此時(shí)lock對(duì)象已經(jīng)被釋放了,所以t1和t3 又可以去競(jìng)爭(zhēng)這個(gè)鎖了,就從阻塞隊(duì)列里面競(jìng)爭(zhēng)鎖。
這里假如t3 競(jìng)爭(zhēng)到了鎖,阻塞隊(duì)列只剩下t1:

然后t3運(yùn)行到了wait方法,釋放鎖,然后進(jìn)入等待隊(duì)列:

然后重復(fù)這些操作~~,最后t1,t2,t3 都進(jìn)入了等待隊(duì)列中,等待notify線程喚醒(這里假設(shè)notify要放在這些線程start后的好幾秒后,因?yàn)閚otify線程也是和這些線程并發(fā)執(zhí)行的,所以等待隊(duì)列中的線程隨時(shí)可能被喚醒)

重點(diǎn)來了:
在等待隊(duì)列中的線程,被notify喚醒之后,會(huì)直接回到阻塞隊(duì)列去競(jìng)爭(zhēng)鎖!??!而不是直接喚醒~
舉個(gè)栗子:
拿notifyAll()來舉例,假如此時(shí)等待隊(duì)列中有三個(gè)線程t1,t2,t3,那么調(diào)用notifyAll()會(huì)直接把它們?nèi)齻€(gè)直接從等待隊(duì)列中進(jìn)入到阻塞隊(duì)列中:

然后再去競(jìng)爭(zhēng)這個(gè)鎖,去執(zhí)行wait之后的代碼~~
五、完整代碼(僅供測(cè)試用)
private static class WaitTask implements Runnable {
private Object lock;
public WaitTask(Object lock) {
this.lock = lock;
}
@Override
public void run() {
synchronized (lock) {
System.out.println(Thread.currentThread().getName() + "準(zhǔn)備進(jìn)入等待狀態(tài)");
// 此線程在等待lock對(duì)象的notify方法喚醒
try {
lock.wait();
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(Thread.currentThread().getName() + "等待結(jié)束,本線程繼續(xù)執(zhí)行");
}
}
}
private static class NotifyTask implements Runnable {
private Object lock;
public NotifyTask(Object lock) {
this.lock = lock;
}
@Override
public void run() {
synchronized (lock) {
System.out.println("準(zhǔn)備喚醒");
// 喚醒所有線程(隨機(jī))
lock.notifyAll();
System.out.println("喚醒結(jié)束");
}
}
}
public static void main(String[] args) throws InterruptedException {
Object lock = new Object();
Object lock2 = new Object();
// 創(chuàng)建三個(gè)等待線程
Thread t1 = new Thread(new WaitTask(lock),"t1");
Thread t2 = new Thread(new WaitTask(lock),"t2");
Thread t3 = new Thread(new WaitTask(lock),"t3");
// 創(chuàng)建一個(gè)喚醒線程
Thread notify = new Thread(new NotifyTask(lock2),"notify線程");
t1.start();
t2.start();
t3.start();
;
Thread.sleep(100);
notify.start();
// 當(dāng)前正在執(zhí)行的線程數(shù)
Thread.sleep(2000);
System.out.println(Thread.activeCount() - 1);
}
六、wait和sleep方法的區(qū)別(面試題):
wait方法是Object類提供的方法,需要搭配synchronized鎖來使用,調(diào)用wait方法會(huì)釋放鎖,線程進(jìn)入WAITING狀態(tài),等待被其他線程喚醒或者超時(shí)自動(dòng)喚醒,喚醒之后的線程需要再次競(jìng)爭(zhēng)synchronized鎖才能繼續(xù)執(zhí)行。
sleep方法是Thread類提供的方法,調(diào)用sleep方法的線程進(jìn)入TIMED_WAITING狀態(tài),不會(huì)釋放鎖,時(shí)間到自動(dòng)喚醒。
總結(jié)
以上就是多線程場(chǎng)景下wait和notify方法的詳解和注意事項(xiàng)了,碼字不易,有幫助的話別忘了關(guān)注,點(diǎn)贊+收藏哦~
來源:blog.csdn.net/qq_43575801/article/details/127601039
胖虎聯(lián)合兩位大佬朋友,知名培訓(xùn)機(jī)構(gòu)講師和科大訊飛架構(gòu),聯(lián)合打造了《Java架構(gòu)師成長(zhǎng)之路》的視頻教程。完全對(duì)標(biāo)外面2萬左右的培訓(xùn)課程。
除了基本的視頻教程之外,還提供了超詳細(xì)的課堂筆記,以及源碼等資料包..
目前課程內(nèi)測(cè)活動(dòng)價(jià):999元,后續(xù)可能會(huì)上調(diào)至 2999元
點(diǎn)擊下方超鏈接查看詳情
(或者點(diǎn)擊文末閱讀原文):
(點(diǎn)擊查看) 2023年,最新Java架構(gòu)師成長(zhǎng)之路 視頻教程!
以下是課程大綱,大家可以長(zhǎng)按識(shí)別查看!


