【Java多線程與併發】——wait/notify

wait/notify爲何而來?

我們知道,線程與線程之間並不是完全獨立的個體,多個線程之間可以通過訪問共享變量,可以實現線程間進行通信。然而當多個線程訪問同一共享變量,如果沒有使用同步機制,即沒有使用synchronize同步方法或者同步代碼塊,我們不確定線程讀到的共享變量的值到底是不是想要的,或者說是準確的值。因此等待/通知機制的出現就滿足解決了這一需求問題。

 

wait與notify實現原理簡介

每個鎖對象都有兩個隊列,一個是就緒隊列,一個是阻塞隊列。就緒隊列存儲了將要獲得鎖的線程,阻塞隊列存儲了被阻塞的線程。一個線程被喚醒後纔會進入到就緒隊列,等待CPU調度;反之如果一個線程被wait後,就會進入到阻塞隊列,等待下一次被喚醒。

如果線程調用了對象的wait()方法,那麼線程便會處於該對象的等待池中,等待池中的線程不會去競爭該對象的鎖。當有線程調用了對象的notifyAll()方法(喚醒所有wait線程)或notify()方法(只隨機喚醒一個wait線程),被喚醒的的線程便會進入該對象的阻塞隊列中,阻塞隊列中的線程會去競爭該對象鎖。 優先級高的線程競爭到對象鎖的概率大,假若某線程沒有競爭到該對象鎖,它還會留在阻塞隊列中,唯有線程再次調用wait()方法,它纔會重新回到等待池中。而競爭到對象鎖的線程則繼續往下執行,直到執行完了synchronized代碼塊,它會釋放掉該對象鎖,這時阻塞隊列中的線程會繼續競爭該對象鎖。

wait()、notify()

方法簡介: 

  • wait()
 public final void wait()  throws InterruptedException,IllegalMonitorStateException

       該方法用來將當前線程置入休眠狀態,直到接到通知或被中斷爲止。在調用wait()之前,線程必須要獲得該對象的對象級別鎖,即只能在同步方法或同步塊中調用wait()方法。進入wait()方法後,當前線程釋放鎖。在從wait()返回前,線程與其他線程競爭重新獲得鎖。如果調用wait()時,沒有持有適當的鎖,則拋出IllegalMonitorStateException,它是RuntimeException的一個子類,因此,不需要try-catch結構。

  • notify()
public final native void notify() throws IllegalMonitorStateException

        該方法也要在同步方法或同步塊中調用,即在調用前,線程也必須要獲得該對象的對象級別鎖,的如果調用notify()時沒有持有適當的鎖,也會拋出IllegalMonitorStateException。

       該方法用來通知那些可能等待該對象的對象鎖的其他線程。如果有多個線程等待,則線程規劃器任意挑選出其中一個wait()狀態的線程來發出通知,並使它等待獲取該對象的對象鎖(notify後,當前線程不會馬上釋放該對象鎖,wait所在的線程並不能馬上獲取該對象鎖,要等到程序退出synchronized代碼塊後,當前線程纔會釋放鎖,wait所在的線程也纔可以獲取該對象鎖),但不驚動其他同樣在等待被該對象notify的線程們。當第一個獲得了該對象鎖的wait線程運行完畢以後,它會釋放掉該對象鎖,此時如果該對象沒有再次使用notify語句,則即便該對象已經空閒,其他wait狀態等待的線程由於沒有得到該對象的通知,會繼續阻塞在wait狀態,直到這個對象發出一個notify或notifyAll。這裏需要注意:它們等待的是被notify或notifyAll,而不是鎖。這與下面的notifyAll()方法執行後的情況不同。

  • notifyAll()
public final native void notifyAll() throws IllegalMonitorStateException

      該方法與notify()方法的工作方式相同,重要的一點差異是:

      notifyAll使所有原來在該對象上wait的線程統統退出wait的狀態(即全部被喚醒,不再等待notify或notifyAll,但由於此時還沒有獲取到該對象鎖,因此還不能繼續往下執行),變成等待獲取該對象上的鎖,一旦該對象鎖被釋放(notifyAll線程退出調用了notifyAll的synchronized代碼塊的時候),他們就會去競爭。如果其中一個線程獲得了該對象鎖,它就會繼續往下執行,在它退出synchronized代碼塊,釋放鎖後,其他的已經被喚醒的線程將會繼續競爭獲取該鎖,一直進行下去,直到所有被喚醒的線程都執行完畢。

  • wait(long)和wait(long,int)

     顯然,這兩個方法是設置等待超時時間的,後者在超值時間上加上ns,精度也難以達到,因此,該方法很少使用。對於前者,如果在等待線程接到通知或被中斷之前,已經超過了指定的毫秒數,則它通過競爭重新獲得鎖,並從wait(long)返回。另外,需要知道,如果設置了超時時間,當wait()返回時,我們不能確定它是因爲接到了通知還是因爲超時而返回的,因爲wait()方法不會返回任何相關的信息。但一般可以通過設置標誌位來判斷,在notify之前改變標誌位的值,在wait()方法後讀取該標誌位的值來判斷,當然爲了保證notify不被遺漏,我們還需要另外一個標誌位來循環判斷是否調用wait()方法。

sleep和wait方法的區別

  • wait是Object的方法,而sleep是Thread特有的方法
  • wait和sleep方法都可以使得線程進入阻塞狀態,都是可中斷方法,被中斷後都會收到中斷異常
  • wait方法的執行必須在同步方法中進行,而sleep則不需要
  • 線程在同步方法中執行sleep方法時,不會釋放monitor的鎖,而wait方法則會釋放monitor的鎖

參考資料

【1】使用wait/notify/notifyAll實現線程間通信的幾點重要說明

【2】《Java高併發編程詳解》

【3】《Java併發編程的藝術》

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章