1. 程式人生 > >執行緒間協作:wait、notify、notifyAll

執行緒間協作: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,因此在子類中不能覆寫任何一個方法。

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

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()方法,那麼執行緒便會處於該物件的等待池中,等待池中的執行緒不會去競爭該物件的鎖。

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

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