Java如何正確的使用wait-notify方法你知道嗎
問題:
sleep() 和 wait() 有什么區(qū)別?
1. sleep(long n) 和 wait(long n) 的區(qū)別
(1) sleep 是 Thread 方法,而 wait 是 Object 的方法 ;
(2) sleep 不需要強(qiáng)制和 synchronized 配合使用,但 wait 需要 和 synchronized 一起用 ;
(3) sleep 在睡眠的同時(shí),不會釋放對象鎖的,但 wait 在等待的時(shí)候會釋放對象鎖 ;
(4) 它們 狀態(tài) TIMED_WAITING;
@Slf4j public class Test1 { private static final Object lock = new Object(); public static void main(String[] args) throws InterruptedException { new Thread(()->{ synchronized (lock){ log.debug("t1線程獲得鎖...."); try { Thread.sleep(20000); } catch (InterruptedException e) { e.printStackTrace(); } } },"t1").start(); // 等待1s,讓線程去執(zhí)行t1線程 Thread.sleep(1000); // 獲取不到鎖,因?yàn)閠1線程在睡眠期間并不會釋放鎖 synchronized (lock){ log.debug("主線程想要獲取鎖...."); } } }
執(zhí)行sleep()方法后的結(jié)果:在線程t1睡眠期間,主線程沒有獲得鎖
10:34:34.574 [t1] DEBUG com.example.test.Test1 - t1線程獲得鎖....
@Slf4j public class Test1 { private static final Object lock = new Object(); public static void main(String[] args) throws InterruptedException { new Thread(()->{ synchronized (lock){ log.debug("t1線程獲得鎖...."); try { lock.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } },"t1").start(); // 等待1s,讓線程去執(zhí)行t1線程 Thread.sleep(1000); // 獲取不到鎖,因?yàn)閠1線程在睡眠期間并不會釋放鎖 synchronized (lock){ log.debug("主線程想要獲取鎖...."); } } }
執(zhí)行wait()方法后的結(jié)果:線程t1等待20s,在線程t1等待期間,主線程獲得了鎖
10:36:22.723 [t1] DEBUG com.example.test.Test1 - t1線程獲得鎖....
10:36:23.721 [main] DEBUG com.example.test.Test1 - 主線程想要獲取鎖....
2. 正確使用wait-notify方法 [while(條件)+wait]
場景:有幾個(gè)小孩都想進(jìn)入房間內(nèi)使用算盤(CPU)進(jìn)行計(jì)算,老王(操作系統(tǒng))就使用了一把鎖(synchronized)讓同一時(shí)間只有一個(gè)小孩能進(jìn)入房間使用算盤,于是他們排隊(duì)進(jìn)入房間。
(1) 小南最先獲取到了鎖,進(jìn)入到房間內(nèi),但是由于條件不滿足(沒煙干不了活),小南不能繼續(xù)進(jìn)行計(jì)算 ,但小南如果一直占用著鎖,其它人就得一直阻塞,效率太低。
(2) 于是老王單開了一間休息室(調(diào)用 wait 方法),讓小南到休息室(WaitSet)等著去了,這時(shí)鎖釋放開, 其它人可以由老王隨機(jī)安排進(jìn)屋
(3) 直到小M將煙送來,大叫一聲 [ 你的煙到了 ] (調(diào)用 notify 方法)
(4) 小南于是可以離開休息室,重新進(jìn)入競爭鎖的隊(duì)列
下面我們看如何正確的實(shí)現(xiàn)這個(gè)場景
2.1 問題1
@Slf4j public class Test2 { private static final Object room = new Object(); // 是否有煙,默認(rèn)沒有 private static boolean hasCigarette = false; public static void main(String[] args) throws InterruptedException { new Thread(()->{ synchronized (room){ log.debug("有煙沒?[{}]", hasCigarette); if(!hasCigarette){ log.debug("沒煙,先歇會!"); try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } } log.debug("有煙沒?[{}]", hasCigarette); if(hasCigarette){ log.debug("有煙,[{}],可以開始干活了", hasCigarette); } } },"小南").start(); // 其他5個(gè)線程也想獲取鎖進(jìn)入房間 for(int i=0;i<5;i++){ new Thread(()->{ synchronized (room){ log.debug("可以開始干活了"); } },"其他人").start(); } // 主線程等待1s Thread.sleep(1000); // 因?yàn)樾∧暇€程使用sleep()方法,因此他在睡眠期間并不釋放鎖,送煙的沒辦法拿到鎖進(jìn)入房間送煙 new Thread(()->{ synchronized (room){ hasCigarette = true; } },"送煙的").start(); } }
執(zhí)行結(jié)果:
11:10:50.556 [小南] DEBUG com.example.test.Test2 - 有煙沒?[false]
11:10:50.565 [小南] DEBUG com.example.test.Test2 - 沒煙,先歇會!
11:10:52.565 [小南] DEBUG com.example.test.Test2 - 有煙沒?[false]
11:10:52.565 [其他人] DEBUG com.example.test.Test2 - 可以開始干活了
11:10:52.565 [其他人] DEBUG com.example.test.Test2 - 可以開始干活了
11:10:52.565 [其他人] DEBUG com.example.test.Test2 - 可以開始干活了
11:10:52.565 [其他人] DEBUG com.example.test.Test2 - 可以開始干活了
11:10:52.565 [其他人] DEBUG com.example.test.Test2 - 可以開始干活了
(1) 小南線程在睡眠期間并不釋放鎖,因此其他線程線程也沒辦法獲取到鎖進(jìn)入房間,送煙線程就沒辦法送煙;
(2) 其它干活的線程,都要一直阻塞,效率太低 ;
要解決上述的問題,需要使用wait-notify機(jī)制
2.2 問題2
@Slf4j public class Test2 { private static final Object room = new Object(); // 是否有煙,默認(rèn)沒有 private static boolean hasCigarette = false; public static void main(String[] args) throws InterruptedException { new Thread(()->{ synchronized (room){ log.debug("有煙沒?[{}]", hasCigarette); if(!hasCigarette){ log.debug("沒煙,先歇會!"); try { room.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } log.debug("有煙沒?[{}]", hasCigarette); if(hasCigarette){ log.debug("有煙,[{}],可以開始干活了", hasCigarette); } } },"小南").start(); // 其他5個(gè)線程也想獲取鎖進(jìn)入房間 for(int i=0;i<5;i++){ new Thread(()->{ synchronized (room){ log.debug("可以開始干活了"); } },"其他人").start(); } // 主線程等待1s Thread.sleep(1000); // 送煙的,喚醒正在睡眠的小南線程 new Thread(()->{ synchronized (room){ hasCigarette = true; room.notify(); } },"送煙的").start(); } }
執(zhí)行結(jié)果:
11:21:36.775 [小南] DEBUG com.example.test.Test2 - 有煙沒?[false]
11:21:36.780 [小南] DEBUG com.example.test.Test2 - 沒煙,先歇會!
11:21:36.780 [其他人] DEBUG com.example.test.Test2 - 可以開始干活了
11:21:36.780 [其他人] DEBUG com.example.test.Test2 - 可以開始干活了
11:21:36.781 [其他人] DEBUG com.example.test.Test2 - 可以開始干活了
11:21:36.781 [其他人] DEBUG com.example.test.Test2 - 可以開始干活了
11:21:36.781 [其他人] DEBUG com.example.test.Test2 - 可以開始干活了
11:21:37.773 [小南] DEBUG com.example.test.Test2 - 有煙沒?[true]
11:21:37.774 [小南] DEBUG com.example.test.Test2 - 有煙,[true],可以開始干活了
解決了其他線程阻塞問題,但是如果有其他線程也在等待呢?就是說等待的線程不止小南一個(gè),那么會不會喚醒錯了呢?
2.3 問題3
@Slf4j public class Test2 { private static final Object room = new Object(); // 是否有煙,默認(rèn)沒有 private static boolean hasCigarette = false; // 是否有外賣,默認(rèn)沒有 static boolean hasTakeout = false; public static void main(String[] args) throws InterruptedException { new Thread(()->{ synchronized (room){ log.debug("有煙沒?[{}]", hasCigarette); if(!hasCigarette){ log.debug("沒煙,先歇會!"); try { room.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } log.debug("有煙沒?[{}]", hasCigarette); if(hasCigarette){ log.debug("有煙,[{}],可以開始干活了", hasCigarette); }else { log.debug("沒干成活.."); } } },"小南").start(); new Thread(()->{ synchronized (room){ log.debug("有外賣沒?[{}]", hasTakeout); if(!hasTakeout){ log.debug("沒外賣,先歇會!"); try { room.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } log.debug("有外賣沒?[{}]", hasTakeout); if(hasTakeout){ log.debug("有外賣,[{}],可以開始干活了", hasTakeout); }else{ log.debug("沒干成活.."); } } },"小女").start(); // 主線程等待1s Thread.sleep(1000); new Thread(()->{ synchronized (room){ hasTakeout = true; log.debug("外賣到了...."); room.notify(); } },"送外賣的").start(); } }
執(zhí)行結(jié)果:送外賣的應(yīng)該叫醒小女但是卻把小南叫醒了
11:31:50.989 [小南] DEBUG com.example.test.Test2 - 有煙沒?[false]
11:31:50.994 [小南] DEBUG com.example.test.Test2 - 沒煙,先歇會!
11:31:50.994 [小女] DEBUG com.example.test.Test2 - 有外賣沒?[false]
11:31:50.994 [小女] DEBUG com.example.test.Test2 - 沒外賣,先歇會!
11:31:51.987 [送外賣的] DEBUG com.example.test.Test2 - 外賣到了....
11:31:51.988 [小南] DEBUG com.example.test.Test2 - 有煙沒?[false]
11:31:51.988 [小南] DEBUG com.example.test.Test2 - 沒干成活..
notify 只能隨機(jī)喚醒一個(gè) WaitSet 中的線程,這時(shí)如果有其它線程也在等待,那么就可能喚醒不了正確的線程,稱之為【虛假喚醒】
2.4 問題4
解決方法:改為 notifyAll
new Thread(()->{ synchronized (room){ hasTakeout = true; log.debug("外賣到了...."); room.notifyAll(); } },"送外賣的").start();
執(zhí)行結(jié)果:
11:34:24.789 [小南] DEBUG com.example.test.Test2 - 有煙沒?[false]
11:34:24.798 [小南] DEBUG com.example.test.Test2 - 沒煙,先歇會!
11:34:24.798 [小女] DEBUG com.example.test.Test2 - 有外賣沒?[false]
11:34:24.802 [小女] DEBUG com.example.test.Test2 - 沒外賣,先歇會!
11:34:25.794 [送外賣的] DEBUG com.example.test.Test2 - 外賣到了....
11:34:25.794 [小女] DEBUG com.example.test.Test2 - 有外賣沒?[true]
11:34:25.794 [小女] DEBUG com.example.test.Test2 - 有外賣,[true],可以開始干活了
11:34:25.794 [小南] DEBUG com.example.test.Test2 - 有煙沒?[false]
11:34:25.795 [小南] DEBUG com.example.test.Test2 - 沒干成活..
從結(jié)果可以看出小女干成活,小南沒有干成活。既然送煙的沒到,小南應(yīng)該繼續(xù)等待才行,等送煙的來了再干活。
用 notifyAll 僅解決某個(gè)線程的喚醒問題,但使用 if + wait 判斷僅有一次機(jī)會,一旦條件不成立,就沒有重新判斷的機(jī)會了
2.5 最終結(jié)果
@Slf4j public class Test2 { private static final Object room = new Object(); // 是否有煙,默認(rèn)沒有 private static boolean hasCigarette = false; // 是否有外賣,默認(rèn)沒有 static boolean hasTakeout = false; public static void main(String[] args) throws InterruptedException { new Thread(()->{ synchronized (room){ log.debug("有煙沒?[{}]", hasCigarette); while (!hasCigarette){ log.debug("沒煙,先歇會!"); try { room.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } log.debug("有煙沒?[{}]", hasCigarette); if(hasCigarette){ log.debug("有煙,[{}],可以開始干活了", hasCigarette); }else { log.debug("沒干成活.."); } } },"小南").start(); new Thread(()->{ synchronized (room){ log.debug("有外賣沒?[{}]", hasTakeout); if(!hasTakeout){ log.debug("沒外賣,先歇會!"); try { room.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } log.debug("有外賣沒?[{}]", hasTakeout); if(hasTakeout){ log.debug("有外賣,[{}],可以開始干活了", hasTakeout); }else{ log.debug("沒干成活.."); } } },"小女").start(); // 主線程等待1s Thread.sleep(1000); // 送煙的,喚醒正在睡眠的小南線程 new Thread(()->{ synchronized (room){ hasTakeout = true; log.debug("外賣到了...."); room.notifyAll(); } },"送外賣的").start(); } } @Slf4j public class Test2 { private static final Object room = new Object(); // 是否有煙,默認(rèn)沒有 private static boolean hasCigarette = false; // 是否有外賣,默認(rèn)沒有 static boolean hasTakeout = false; public static void main(String[] args) throws InterruptedException { new Thread(()->{ synchronized (room){ log.debug("有煙沒?[{}]", hasCigarette); while (!hasCigarette){ log.debug("沒煙,先歇會!"); try { room.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } log.debug("有煙沒?[{}]", hasCigarette); if(hasCigarette){ log.debug("有煙,[{}],可以開始干活了", hasCigarette); }else { log.debug("沒干成活.."); } } },"小南").start(); new Thread(()->{ synchronized (room){ log.debug("有外賣沒?[{}]", hasTakeout); if(!hasTakeout){ log.debug("沒外賣,先歇會!"); try { room.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } log.debug("有外賣沒?[{}]", hasTakeout); if(hasTakeout){ log.debug("有外賣,[{}],可以開始干活了", hasTakeout); }else{ log.debug("沒干成活.."); } } },"小女").start(); // 主線程等待1s Thread.sleep(1000); // 送煙的,喚醒正在睡眠的小南線程 new Thread(()->{ synchronized (room){ hasTakeout = true; log.debug("外賣到了...."); room.notifyAll(); } },"送外賣的").start(); } }
執(zhí)行結(jié)果:當(dāng)沒煙的時(shí)候,小南線程繼續(xù)等待,等待下一次判斷有煙的時(shí)候再干活
11:38:36.206 [小南] DEBUG com.example.test.Test2 - 有煙沒?[false]
11:38:36.212 [小南] DEBUG com.example.test.Test2 - 沒煙,先歇會!
11:38:36.212 [小女] DEBUG com.example.test.Test2 - 有外賣沒?[false]
11:38:36.212 [小女] DEBUG com.example.test.Test2 - 沒外賣,先歇會!
11:38:37.205 [送外賣的] DEBUG com.example.test.Test2 - 外賣到了....
11:38:37.205 [小女] DEBUG com.example.test.Test2 - 有外賣沒?[true]
11:38:37.205 [小女] DEBUG com.example.test.Test2 - 有外賣,[true],可以開始干活了
11:38:37.205 [小南] DEBUG com.example.test.Test2 - 沒煙,先歇會!
使用wait-notify的正確姿勢:
synchronized(lock) { while(條件不成立) { lock.wait(); } } //另一個(gè)線程 synchronized(lock) { lock.notifyAll(); }
調(diào)用wait()和notify()系列方法進(jìn)行線程通信的要點(diǎn)如下:
(1) 調(diào)用某個(gè)同步對象locko的wait()和notify()類型方法前,必須要取得這個(gè)鎖對象的監(jiān)視鎖,所以wait()和notify()類型方法必須放在synchronized(locko)同步塊中,如果沒有獲得監(jiān)視鎖,JVM就會報(bào)IllegalMonitorStateException異常。
(2) 調(diào)用wait()方法時(shí)使用while進(jìn)行條件判斷,如果是在某種條件下進(jìn)行等待,對條件的判斷就不能使用if語句做一次性判斷,而是使用while循環(huán)進(jìn)行反復(fù)判斷。只有這樣才能在線程被喚醒后繼續(xù)檢查wait的條件,并在條件沒有滿足的情況下繼續(xù)等待。
總結(jié)
本篇文章就到這里了,希望能夠給你帶來幫助,也希望您能夠多多關(guān)注腳本之家的更多內(nèi)容!
- Java多線程通信wait()和notify()代碼實(shí)例
- Object類wait及notify方法原理實(shí)例解析
- Java object wait notify notifyAll代碼解析
- Java多線程中的wait/notify通信模式實(shí)例詳解
- 分析java并發(fā)中的wait notify notifyAll
- java wait()/notify() 實(shí)現(xiàn)生產(chǎn)者消費(fèi)者模式詳解
- 基于線程的wait和notify使用,生產(chǎn)消費(fèi)案例
- 聊聊Object類中的wait()和notify()方法
- 為什么wait和notify必須放在synchronized中使用
相關(guān)文章
Java中如何將?int[]?數(shù)組轉(zhuǎn)換為?ArrayList(list)
這篇文章主要介紹了Java中將?int[]?數(shù)組?轉(zhuǎn)換為?List(ArrayList),本文通過示例代碼給大家講解的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-12-12java 避免出現(xiàn)NullPointerException(空指針)的方法總結(jié)
這篇文章主要介紹了java 避免出現(xiàn)NullPointerException(空指針)的方法總結(jié)的相關(guān)資料,需要的朋友可以參考下2017-09-09NoHttpResponseException問題分析解決記錄
這篇文章主要為大家介紹了NoHttpResponseException問題分析解決記錄,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-08-08一次java異步任務(wù)的實(shí)戰(zhàn)記錄
最近做項(xiàng)目的時(shí)候遇到了一個(gè)小問題,從前臺提交到服務(wù)端A,A調(diào)用服務(wù)端B處理超時(shí),下面這篇文章主要給大家介紹了一次java異步任務(wù)的實(shí)戰(zhàn)記錄,需要的朋友可以參考下2022-05-05JetBrains IntelliJ IDEA 2020安裝與使用教程詳解
這篇文章主要介紹了JetBrains IntelliJ IDEA 2020安裝與使用教程,本文通過圖文并茂的形式給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-06-06