經常在往上逛,關於在java中notify和notifyAll,經常有人有以下的說法:
notify只會通知一個在等待的對象,而notifyAll會通知所有在等待的對象,並且所有對象都會繼續運行
並且,好像都有例子可以證明。上面的說法,可以說對,也可以說不對。究其原因,在於其中有一點很關鍵,官方的說法如下所示:
wait,notify,notifyAll:
此方法只應由作為此對象監視器的所有者的線程來調用。通過以下三種方法之一,線程可以成為此對象監視器的所有者
:
通過執行此對象的同步執行個體方法。
通過執行在此對象上進行同步的 synchronized 語句的本文。
對於 Class 類型的對象,可以通過執行該類的同步靜態方法。
一次只能有一個線程擁有對象的監視器。
以上說法,摘自javadoc。意思即,在調用中,必須持有對象監視器(即鎖),我們可以理解為需要在synchronized方法內運行。那麼由此話的隱含意思,即如果要繼續由同步塊包含的代碼塊,需要重新擷取鎖才可以。這句話,在javadoc中這樣描述:
wait
此方法導致當前線程(稱之為 T)將其自身放置在對象的等待集中,然後放棄此對象上的所有同步要求。出於線程調度
目的,在發生以下四種情況之一前,線程 T 被禁用,且處於休眠狀態:
其他某個線程調用此對象的 notify 方法,並且線程 T 碰巧被任選為被喚醒的線程。
其他某個線程調用此對象的 notifyAll 方法。
其他某個線程中斷線程 T。
大約已經到達指定的實際時間。但是,如果 timeout 為零,則不考慮實際時間,在獲得通知前該線程將一直等待。
然後,從對象的等待集中刪除線程 T,並重新進行線程調度。然後,該線程以常規方式與其他線程競爭,以獲得在該對
象上同步的權利;一旦獲得對該對象的控制權,該對象上的所有其同步聲明都將被恢複到以前的狀態,這就是調用 wait
方法時的情況。然後,線程 T 從 wait 方法的調用中返回。所以,從 wait 方法返回時,該對象和線程 T 的同步狀態與調
用 wait 方法時的情況完全相同。
即必須重新進行擷取鎖,這樣對於notifyAll來說,雖然所有的線程都被通知了。但是這些線程都會進行競爭,且只會有一個線程成功擷取到鎖,在這個線程沒有執行完畢之前,其他的線程就必須等待了(只是這裡不需要再notifyAll通知了,因為已經notifyAll了,只差擷取鎖了)有如下一個代碼,可以重現這個現象。
首先,定義一個可以啟動並執行線程類,如下所示:
private static final Object obj = new Object(); static class R implements Runnable { int i; R(int i) { this.i = i; } public void run() { try { synchronized(obj) { System.out.println("線程-> " + i + " 等待中"); obj.wait(); System.out.println("線程-> " + i + " 在運行了"); Thread.sleep(30000); } } catch(Exception e) { e.printStackTrace(); } } }
注意上面的run方法內部,我們在wait()之後,列印一句話,然後將當前代碼,暫停30秒。關於sleep方法,是這樣描述的:
該線程不丟失任何監視器的所屬權。
即仍然持有鎖。
然後,定義一個main方法來運行這些線程,如下所示:
Thread[] rs = new Thread[10]; for(int i = 0;i < 10;i++) { rs[i] = new Thread(new R(i)); } for(Thread r : rs) { r.start(); } Thread.sleep(5000); synchronized(obj) { obj.notifyAll(); }
我們定義了10個線程,然後全部運行之。因為有wait,10個線程都會在列印出 “開始運行”之後等待。然後main方法調用notifyAll。這裡的輸出就會出現如下的輸出:
線程-> 0 等待中
線程-> 4 等待中
線程-> 5 等待中
線程-> 3 等待中
線程-> 2 等待中
線程-> 1 等待中
線程-> 6 等待中
線程-> 7 等待中
線程-> 8 等待中
線程-> 9 等待中
線程-> 9 在運行了
...30秒之內,不會有其他輸出
在上面的輸出中,在wait之後,只有一個線程輸出了”在運行了”語句,並且在一段時間內(這裡為30秒),不會有其他輸出。即表示,在當前代碼持有鎖之間,其他線程是不會輸出的。
最後結論就是:被wait的線程,想要繼續啟動並執行話,它必須滿足2個條件:
由其他線程notify或notifyAll了,並且當前線程被通知到了
經過和其他線程進行鎖競爭,成功擷取到鎖了2個條件,缺一不可。其實在實現層面,notify和notifyAll都達到相同的效果,會有一個線程繼續運行。但notifyAll免去了,線程運行完了通知其他線程的必要,因為已經通知過了。什麼時候用notify,什麼時候使用notifyAll,這就得看實際的情況了。
以上就是對Java notify和NotifyAll的資料整理,後續繼續補充相關資料謝謝大家對本站的支援!