通俗的深入解析notify()/notifyAll()和wait()方法

目錄

 

前言

什麼是monitor?

爲什麼sleep在Thread中定義?而wait三種方法在Object類中定義?

notify()/notifyAll()和wait()的使用、詮釋和剖析

wait 必須在 synchronized 保護的同步代碼中使用 

虛假喚醒(spurious wakeup)

 notify()和notifyAll()

總結一下wait與Sleep()的區別


接着上文JAVA線程基礎看這篇文章就夠了寫補充:

前言

notify()/notifyAll()和wait()三個方法簡單的說是喚醒、喚醒所有、讓線程等待,wait()與sleep()是不同的——名稱不同效果肯定不同。然而同志們往往只知其然不知其所以然。打開編譯器點擊notify()/notifyAll()和wait()這三個方法會發現其實官方源碼有註釋,閱讀官方的註釋當然能夠事半功倍,我猜你一定懶得看英文,就讓我這個水過6級英語的筆者來替你翻譯。

什麼是monitor?

閱讀源碼註釋前我們先了解一下頻繁出現的monitor這個詞:在JVM的規範中,有這麼一些話:   

  1. “在JVM中,每個對象和類在邏輯上都是和一個監視器相關聯的”   (監視器的來由)
  2. “爲了實現監視器的排他性監視能力,JVM爲每一個對象和類都關聯一個鎖”(監視器與對象鎖的關係)   
  3. “鎖住了一個對象,就是獲得對象相關聯的監視器”(監視器與鎖對象的關係)

翻譯成人話就是:每個對象都有一個稱之爲monitor監視器的鎖,這個鎖是對象級別的,只要是對象就有。

爲什麼sleep在Thread中定義?而wait三種方法在Object類中定義?

上面我們說到,monitor是對象級別的,只要是對象就有。所以notify()/notifyAll()和wait()在Object中定義是合適、正確的。那爲什麼sleep在Thread中呢?

  • sleep()的作用是讓sleep方法所在的當前線程暫停運行一段時間,其控制範圍是由當前線程決定,也就是說得要是線程才能sleep()。
  • 這麼看來object中定義的方法受衆是對象,而Thread中定義的方法受衆是線程,二者的受衆範圍決定了他們定義在不同的類中。

notify()/notifyAll()和wait()的使用、詮釋和剖析

wait 必須在 synchronized 保護的同步代碼中使用 

     * The current thread must own this object's monitor. The thread
     * releases ownership of this monitor and waits until another thread
     * notifies threads waiting on this object's monitor to wake up
     * either

【舉手】我看得懂我來翻譯:當前線程必須要擁有當前對象的監視器(可以理解爲要有這個對象的鎖,即在synchronized(obj){...}裏面,由obj來調用wait方法)。該線程釋放他所擁有的監視器(鎖)直到其它線程喚醒該線程的監視器(鎖)。對於wait方法的解釋就到這裏了,且編寫wait的作者考慮的了”虛假喚醒“和其它情況。

這裏提及了三個事情:

  1. 使用wait()的前提:擁有當前的monitor(對象鎖),即在synchronized代碼塊裏面
  2. wait後線程會釋放鎖(以便被notify拿到鎖喚醒),直到被喚醒。
  3. 因爲特殊情況(虛假喚醒等),正確使用它又特別的語法,如下:

虛假喚醒(spurious wakeup)

線程可能在既沒有被notify/notifyAll,也沒有被中斷或者超時的情況下被喚醒,稱之爲虛假喚醒(spurious wakeup)。

     * As in the one argument version, interrupts and spurious wakeups are
     * possible, and this method should always be used in a loop:
     * <pre>
     *     synchronized (obj) {
     *         while (&lt;condition does not hold&gt;)
     *             obj.wait();
     *         ... // Perform action appropriate to condition
     *     }
     * </pre>

【舉手】我還是能看懂:當在只有一個參數的版本(一把鎖),中止和虛假喚醒是可能存在的,那麼這個方法(wait)需要經常使用到一個循環:舉例:

  • 代碼解釋:while不斷的判斷條件是否滿足,不滿足執行obj.wait()方法,讓他不斷的wait,wait個夠。反正它資源第一次wait就釋放了,如果存在虛假喚醒或者不可預測的中止情況,可以馬上糾正過來。

 notify()和notifyAll()

notify()、notifyAll()喚醒單個或所有使用了wait()方法的線程。他們兩個是對應配合着喚醒被wait的線程的,因此前置條件和wait一樣,都要在synchronized代碼塊裏面 用。同時需要注意的有,面試題目一般會涉及這裏,因爲這兩個方法的存在,所以喚醒wait()的方法可不止一個哦~

       * Wakes up a single thread that is waiting on this object's
     * monitor. If any threads are waiting on this object, one of them
     * is chosen to be awakened. The choice is arbitrary and occurs at
     * the discretion of the implementation.

【舉手】沒錯又是我,除了我還有誰:喚醒正在這個對象的監視器上等待的單個線程。如果有不止一個線程在這個對象上等待,選擇其中一個喚醒。這個選擇是隨機的,具體如何選擇由實際實現決定。

  • 實際上線程wait會進入該對象的wait隊列,notify隨機喚醒,但是經常是喚醒等待時間最長的那個(就近原則?)。
     * The awakened thread will not be able to proceed until the current
     * thread relinquishes the lock on this object. The awakened thread will
     * compete in the usual manner with any other threads that might be
     * actively competing to synchronize on this object; for example, the
     * awakened thread enjoys no reliable privilege or disadvantage in being
     * the next thread to lock this object.

 

【舉手】參考了下搜索引擎翻譯,翻譯出的語句我反正看不懂,還是要看自己翻譯一下:被喚醒的線程將無法繼續(接着wait後面馬上)運行,直到當前線程放棄對該對象的鎖定(用這個鎖的線程把鎖空出來)。喚醒的線將以平常的方式與任何其他可能主動競爭該鎖(sycronized代碼塊的使用權);例如喚醒的線程在存在時沒有可靠的權限或處於劣勢鎖定此對象的下一個線程。(生澀的很,看下面)

  • wait()方法會馬上釋放鎖,但是notify被喚醒後不會馬上獲得這個鎖。它將按照正常的方式(排序唄,進入就緒狀態)與其它在排隊使用該鎖的對象一起競爭。而且搶到使用權後,它將會一直執行完sycronized代碼後纔會釋放該鎖

notifyAll就不解釋了,如其名ALL全部喚醒。

總結一下wait與Sleep()的區別

首先我們瞭解一下幾種方法的屬於:

  • notify()/notifyAll()和wait()三種方法屬於Obeject類,而sleep()方法屬於Thread類

我們再來了解一下wait和sleep的區別:

  1. notify()/notifyAll()和wait()必須要在鎖中;
  2. wait()調用後會釋放鎖。sleep不會,他會一直佔有。
  3. wait直到喚醒前要一直等待,而sleep就定好時間了。

他們的共同點

  1. 都能想要interrupt標記位,拋出interruptedException:具體interrupt原理博客
  2. 都能阻塞線程。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章