1. 程式人生 > >【Java併發程式設計】之十:使用wait/notify/notifyAll實現執行緒間通訊的幾點重要說明

【Java併發程式設計】之十:使用wait/notify/notifyAll實現執行緒間通訊的幾點重要說明

在Java中,可以通過配合呼叫Object物件的wait()方法和notify()方法或notifyAll()方法來實現執行緒間的通訊。線上程中呼叫wait()方法,將阻塞等待其他執行緒的通知(其他執行緒呼叫notify()方法或notifyAll()方法),線上程中呼叫notify()方法或notifyAll()方法,將通知其他執行緒從wait()方法處返回。

      Object是所有類的超類,它有5個方法組成了等待/通知機制的核心:notify()、notifyAll()、wait()、wait(long)和wait(long,int)。在Java中,所有的類都從Object繼承而來,因此,所有的類都擁有這些共有方法可供使用。而且,由於他們都被宣告為final,因此在子類中不能覆寫任何一個方法。

     這裡詳細說明一下各個方法在使用中需要注意的幾點:

      1、wait()

      public final void wait()  throws InterruptedException,IllegalMonitorStateException

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

     2、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()方法執行後的情況不同。 

     3、notifyAll()

     public final native void notifyAll() throws IllegalMonitorStateException

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

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

     4、wait(long)和wait(long,int)

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

       深入理解:

   如果執行緒呼叫了物件的wait()方法,那麼執行緒便會處於該物件的等待池中,等待池中的執行緒不會去競爭該物件的鎖。

   當有執行緒呼叫了物件的notifyAll()方法(喚醒所有wait執行緒)或notify()方法(只隨機喚醒一個wait執行緒),被喚醒的的執行緒便會進入該物件的鎖池中,鎖池中的執行緒會去競爭該物件鎖。

   優先順序高的執行緒競爭到物件鎖的概率大,假若某執行緒沒有競爭到該物件鎖,它還會留在鎖池中,唯有執行緒再次呼叫wait()方法,它才會重新回到等待池中。而競爭到物件鎖的執行緒則繼續往下執行,直到執行完了synchronized程式碼塊,它會釋放掉該物件鎖,這時鎖池中的執行緒會繼續競爭該物件鎖。