一、線程間等待與喚醒機制
wait()和notify()是Object類的方法,用于線程的等待與喚醒,必須搭配synchronized 鎖來使用。
多線程并發的場景下,有時需要某些線程先執行,這些線程執行結束后其他線程再繼續執行。
比如: 一個長跑比賽,裁判員要等跑步運動員沖線了才能宣判比賽結束,那裁判員線程就得等待所有的運動員線程運行結束后,再喚醒這個裁判線程。
二、等待方法wait()
wait 做的事情:
使當前執行代碼的線程進行等待. (把線程放到等待隊列中)
釋放當前的鎖
滿足一定條件時被喚醒, 重新嘗試獲取這個鎖.
wait 要搭配 synchronized 來使用. 脫離 synchronized 使用 wait 會直接拋出異常.
wait 結束等待的條件:
其他線程調用該對象的 notify 方法.
wait 等待時間超時 (wait 方法提供一個帶有 timeout 參數的版本, 來指定等待時間).
其他線程調用該等待線程的 interrupted 方法, 導致 wait 拋出 InterruptedException 異常.
注意事項:
調用wait()方法的前提是首先要獲取該對象的鎖(synchronize對象鎖)
調用wait()方法會釋放鎖,本線程進入等待隊列等待被喚醒,被喚醒后不是立即恢復執行,而是進入阻塞隊列,競爭鎖
等待方法:
1.癡漢方法,死等,線程進入阻塞態(WAITING)直到有其他線程調用notify方法喚醒
2.等待一段時間,若在該時間內線程被喚醒,則繼續執行,若超過相應時間還沒有其他線程喚醒此線程,此線程不再等待,恢復執行。
調用wait方法之后:
三、喚醒方法notify()
notify 方法是喚醒等待的線程.
方法notify()也要在同步方法或同步塊中調用,該方法是用來通知那些可能等待該對象的對象鎖的其它線程,對其發出通知notify,并使它們重新獲取該對象的對象鎖。
如果有多個線程等待,則有線程調度器隨機挑選出一個呈 wait 狀態的線程。(并沒有 “先來后到”)
在notify()方法后,當前線程不會馬上釋放該對象鎖,要等到執行notify()方法的線程將程序執行完,也就是退出同步代碼塊之后才會釋放對象鎖。
注意事項:
notify():隨機喚醒一個處在等待狀態的線程。
notifyAll():喚醒所有處在等待狀態的線程。
無論是wait還是notify方法,都需要搭配synchronized鎖來使用(等待和喚醒,也是需要對象)
四、關于wait和notify內部等待問題(重要)
對于wait和notify方法,其實有一個阻塞隊列也有一個等待隊列。
阻塞隊列表示同一時間只有一個線程能獲取到鎖,其他線程進入阻塞隊列
等待隊列表示調用wait (首先此線程要獲取到鎖,進入等待隊列,釋放鎖)
舉個栗子:
現有如下定義的等待線程任務
privatestaticclassWaitTaskimplementsRunnable{ privateObjectlock; publicWaitTask(Objectlock){ this.lock=lock; } @Override publicvoidrun(){ synchronized(lock){ System.out.println(Thread.currentThread().getName()+"準備進入等待狀態"); //此線程在等待lock對象的notify方法喚醒 try{ lock.wait(); Thread.sleep(1000); }catch(InterruptedExceptione){ thrownewRuntimeException(e); } System.out.println(Thread.currentThread().getName()+"等待結束,本線程繼續執行"); } } }
然后創建三個等待線程:
由于同一時間只有一個線程(隨機調度)能獲取到synchronized鎖,所以會有兩個線程沒競爭到鎖,從而進入了阻塞隊列。
這里假如t2先競爭到了鎖,所以先會阻塞t1和t3:
又由于調用wait方法會釋放鎖,調用wait方法的線程t2就會進入等待隊列,直到被notify喚醒或者超時自動喚醒。
然后此時lock對象已經被釋放了,所以t1和t3 又可以去競爭這個鎖了,就從阻塞隊列里面競爭鎖。
這里假如t3 競爭到了鎖,阻塞隊列只剩下t1:
然后t3運行到了wait方法,釋放鎖,然后進入等待隊列:
然后重復這些操作~~,最后t1,t2,t3 都進入了等待隊列中,等待notify線程喚醒(這里假設notify要放在這些線程start后的好幾秒后,因為notify線程也是和這些線程并發執行的,所以等待隊列中的線程隨時可能被喚醒)
重點來了:
在等待隊列中的線程,被notify喚醒之后,會直接回到阻塞隊列去競爭鎖?。?!而不是直接喚醒~
舉個栗子:
拿notifyAll()來舉例,假如此時等待隊列中有三個線程t1,t2,t3,那么調用notifyAll()會直接把它們三個直接從等待隊列中進入到阻塞隊列中:
然后再去競爭這個鎖,去執行wait之后的代碼~~
五、完整代碼(僅供測試用)
privatestaticclassWaitTaskimplementsRunnable{ privateObjectlock; publicWaitTask(Objectlock){ this.lock=lock; } @Override publicvoidrun(){ synchronized(lock){ System.out.println(Thread.currentThread().getName()+"準備進入等待狀態"); //此線程在等待lock對象的notify方法喚醒 try{ lock.wait(); Thread.sleep(1000); }catch(InterruptedExceptione){ thrownewRuntimeException(e); } System.out.println(Thread.currentThread().getName()+"等待結束,本線程繼續執行"); } } } privatestaticclassNotifyTaskimplementsRunnable{ privateObjectlock; publicNotifyTask(Objectlock){ this.lock=lock; } @Override publicvoidrun(){ synchronized(lock){ System.out.println("準備喚醒"); //喚醒所有線程(隨機) lock.notifyAll(); System.out.println("喚醒結束"); } } } publicstaticvoidmain(String[]args)throwsInterruptedException{ Objectlock=newObject(); Objectlock2=newObject(); //創建三個等待線程 Threadt1=newThread(newWaitTask(lock),"t1"); Threadt2=newThread(newWaitTask(lock),"t2"); Threadt3=newThread(newWaitTask(lock),"t3"); //創建一個喚醒線程 Threadnotify=newThread(newNotifyTask(lock2),"notify線程"); t1.start(); t2.start(); t3.start(); ; Thread.sleep(100); notify.start(); //當前正在執行的線程數 Thread.sleep(2000); System.out.println(Thread.activeCount()-1); }
六、wait和sleep方法的區別(面試題):
wait方法是Object類提供的方法,需要搭配synchronized鎖來使用,調用wait方法會釋放鎖,線程進入WAITING狀態,等待被其他線程喚醒或者超時自動喚醒,喚醒之后的線程需要再次競爭synchronized鎖才能繼續執行。
sleep方法是Thread類提供的方法,調用sleep方法的線程進入TIMED_WAITING狀態,不會釋放鎖,時間到自動喚醒。
總結
以上就是多線程場景下wait和notify方法的詳解和注意事項了,碼字不易,有幫助的話別忘了關注,點贊+收藏哦~
審核編輯:湯梓紅
-
JAVA
+關注
關注
19文章
2975瀏覽量
105150 -
多線程
+關注
關注
0文章
278瀏覽量
20075 -
代碼
+關注
關注
30文章
4828瀏覽量
69055 -
線程
+關注
關注
0文章
505瀏覽量
19758 -
WAIT
+關注
關注
0文章
4瀏覽量
2528
原文標題:圖解 Java多線程中的 wait() 和 notify() 方法
文章出處:【微信號:AndroidPush,微信公眾號:Android編程精選】歡迎添加關注!文章轉載請注明出處。
發布評論請先 登錄
相關推薦
評論