JAVA中wait()和notify()如何使用詳解
前言
大家應該都知道,線程之間是搶占式隨機執(zhí)行的,但是我們并不希望這樣。因為這樣非?;靵y,并不好去預估程序的執(zhí)行結果。我們甚至希望,線程之間能夠配合執(zhí)行,那么我們就可以使用wait()和notify()來做到。
一、wait()方法
wait有兩個方法:
wait():讓當前線程進入等待(阻塞)狀態(tài)。死等,沒有喚醒就會一直阻塞
wait(long timeout) :指定時間內,讓線程進入等待(阻塞)狀態(tài)。
1.wait()主要做的事
- 使當前執(zhí)行代碼的線程進行等待。(把線程放到等待隊列中)
- 釋放當前的鎖
- 滿足條件被notify()喚醒,重新嘗試獲取這個鎖
public static void main(String[] args) throws InterruptedException { Object object = new Object(); System.out.println("wait 之前"); /** * Object中有wait方法,通過Object對象調用wait方法 * wait沒有參數(shù)的版本是默認死等 * wait帶參數(shù)的版本是指定超時時間,如果超時了還沒有notify就繼續(xù)執(zhí)行 * * 使用wait有三個操作: * (1)釋放鎖 * (2)進入阻塞等待,準備接受通知 * (3)收到通知之后喚醒,并且重新獲取鎖 */ /** * 因為wait會解鎖,所以wait必須在synchronized內部 * 而且synchronized括號內的Object對象,必須和調用wait的是同一對象 */ synchronized(object) { object.wait(); } System.out.println("wait 之后"); }
2.wait()的結束條件
其他線程調?該對象的 notify ?法.
wait 等待時間超時 (wait ?法提供?個帶有 timeout 參數(shù)的版本, 來指定等待時間).
其他線程調?該等待線程的 interrupt ?法, 導致 wait 拋出 InterruptedException 異常,并清除中斷標志位(給程序員自由發(fā)揮的空間),并重新嘗試獲取鎖。
public static void main(String[] args) throws InterruptedException { Object locker = new Object(); Thread t1 = new Thread(() -> { //獲取當前線程 Thread current = Thread.currentThread(); int count = 0; //標志位默認為false,為true就是被中斷了 while (!current.isInterrupted()) { //如果中斷位被清空了,不會執(zhí)行第二次 System.out.println("t1線程第一次執(zhí)行" + count++); synchronized (locker) { //因為Thread并沒有處理這個異常,所以必須在這里使用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之后 耗時:" + (end - start)); }); t1.start(); }
結果:
二、notify()
notify?法是喚醒等待的線程,主要配合wait():
notify():喚醒同一對象調用正在wait()的線程,如果有多個線程正在wait()就會隨機喚醒一個線程
notifyAll():喚醒所有正在wait()的線程
1.notify()主要做的事
?法notify()也要在同步?法或同步塊中調?,該?法是?來通知那些可能等待該對象的對象鎖的其它線程,對其發(fā)出通知notify,并使它們重新獲取該對象的對象鎖。
如果有多個線程等待,則有線程調度器隨機挑選出?個呈 wait 狀態(tài)的線程。(并沒有 "先來后到")
在notify()?法后,當前線程不會?上釋放該對象鎖,要等到執(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之后,并不會馬上釋放鎖結束,至少會 //把synchronized中的語句執(zhí)行完 System.out.println("未解鎖之后的notify"); } //看是否會執(zhí)行到這一條語句 System.err.println("解鎖之后的notify"); }); t1.start(); //防止執(zhí)行到notify了,t2還沒阻塞(wait) Thread.sleep(500); t2.start(); }
結果分析:
注意事項:
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(); }
結果:
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線程結束"); }); Thread t2 = new Thread(() -> { System.out.println("t2線程開始"); synchronized (locker) { try { locker.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } System.err.println("t2線程結束"); }); Thread t3 = new Thread(() -> { System.out.println("t3線程開始"); synchronized (locker) { try { locker.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } System.err.println("t3線程結束"); }); Thread t4 = new Thread(() -> { synchronized (locker) { System.out.println("notify之前"); locker.notifyAll(); System.out.println("notify之后"); } }); t1.start(); t2.start(); t3.start(); //保證上面的線程都已經執(zhí)行了wait Thread.sleep(1000); t4.start(); }
三、調用 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線程結束"); }); Thread t4 = new Thread(() -> { try { Thread.sleep(1000); locker.notify(); } catch (InterruptedException e) { e.printStackTrace(); } }); t1.start(); t4.start(); }
并且synchronized括號內部,必須和調用的是同一對象,不然依然會拋異常:
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線程結束"); } }); Thread t4 = new Thread(() -> { synchronized (locker1) { try { Thread.sleep(1000); locker2.notify(); } catch (InterruptedException e) { e.printStackTrace(); } } }); t1.start(); t4.start(); }
四、wait和sleep的比較/區(qū)別(面試題)
其實理論上 wait 和 sleep 完全是沒有可?性的,因為?個是?于線程之間的通信的,?個是讓線程阻塞?段時間。
相同點:
都會讓線程放棄執(zhí)行一段時間
都可以被interrupt喚醒,并且都會拋出 InterruptedException 異常,并且清空標志位
不同點:
wait是Object的方法,sleep是Thread的靜態(tài)方法
wait必須在synchronized中,sleep不需要
wait阻塞的時候釋放鎖,sleep并不會,sleep會抱著鎖一起阻塞
wait用于線程間通信(如生產者-消費者模型),sleep用于阻塞線程
總結
到此這篇關于JAVA中wait()和notify()如何使用的文章就介紹到這了,更多相關Java wait()和notify()內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
Mybatis如何傳入多個參數(shù)的實現(xiàn)代碼
這篇文章主要介紹了Mybatis如何傳入多個參數(shù)的實現(xiàn)代碼,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2019-12-12ElasticSearch不停機重建索引延伸思考及優(yōu)化詳解
這篇文章主要為大家介紹了ElasticSearch不停機重建索引延伸思考及優(yōu)化詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-02-02SpringBoot中注解@ConfigurationProperties與@Value的區(qū)別與使用詳解
本文主要介紹了SpringBoot中注解@ConfigurationProperties與@Value的區(qū)別與使用,具有一定的參考價值,感興趣的小伙伴們可以參考一下2021-09-09帶你了解Java數(shù)據(jù)結構和算法之無權無向圖
這篇文章主要為大家介紹了Java數(shù)據(jù)結構和算法之無權無向圖?,具有一定的參考價值,感興趣的小伙伴們可以參考一下,希望能夠給你帶來幫助2022-01-01