多線程-wait/notify/notifyAll

來源:互聯網
上載者:User

標籤:list   timeout   exce   blog   競爭   ges   父類   start   多線程   

引言

在Java中,可以通過配合調用Object對象的wait,notify和notifyAll來實現線程間的通訊。

線上程中調用wait方法,將阻塞帶帶其他線程的通知(其他線程調用notify或notifyAll)。

線上程中調用notify或notifyAll將通知其他線程從wait方法處返回。

Object是所有類的父類,它有5個方法組成了等待/通知機制的核心:notify(),notifyAll(),wait(),wait(long)和wait(long, int)。

在Java中,所有的類都從Object繼承而來,因此所有的類都擁有這些共有方法。而且由於他們都被聲明為final,因此在子類中不能覆寫任何一個方法。

public class Object {    public final native void notify();    public final native void notifyAll();    public final void wait() throws InterruptedException {        wait(0);    }        public final native void wait(long timeout) throws InterruptedException;    public final void wait(long timeout, int nanos) throws InterruptedException {        if (timeout < 0) {            throw new IllegalArgumentException("timeout value is negative");        }        if (nanos < 0 || nanos > 999999) {            throw new IllegalArgumentException(                                "nanosecond timeout value out of range");        }        if (nanos > 0) {            timeout++;        }        wait(timeout);    }}

 

wait  

該方法用來將當前線程置於休眠狀態,直到接到通知或被中斷,在調用wait方法之前,線程必須要獲得該對象層級鎖,否則編譯可以通過,但是運行會拋出異常IllegalMonitorStateException(它是RuntimeException的一個子類,不需要try-catch結構),即只能在同步方法或同步塊中調用wait。進入wait方法後,當前線程阻塞,並立刻釋放鎖。在從wait返回前,線程與其他線程競爭重新獲得鎖。

(1)線程必須要獲得該對象層級鎖

package com.huawei.thread;public class Test36 {public static void main(String[] args) throws InterruptedException {Object obj = new Object();obj.wait();}}

運行:

wait(long)/wait(long, int)

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

 

 

notify

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

 執行個體:調用lock對象的notify方法,但是擷取的鎖卻是obj對象,所以拋出異常。

package com.huawei.thread;public class Test36 {public static void main(String[] args) throws InterruptedException {Object obj = new Object();Object lock = new Object();synchronized (obj) {lock.notify();}}}

運行:

(2)該方法用來通知那些可能等待該對象的對象鎖的其他線程。如果有多個線程等待,則線程規劃器任意挑選出其中一個wait狀態的線程來發出通知,並使它等待擷取該對象的對象鎖。

package com.huawei.thread;public class Test36 {public static void main(String[] args) throws InterruptedException {Object obj = new Object();Thread a = new Thread(new A(obj));Thread b = new Thread(new B(obj));a.setName("a");b.setName("b");a.start();b.start();Thread.sleep(2000);synchronized (obj) {obj.notify();}}}class A implements Runnable {private Object obj;public A(Object obj) {this.obj = obj;}@Overridepublic void run() {synchronized (obj) {try {System.out.println(Thread.currentThread().getName() + " is waiting...");obj.wait();System.out.println(Thread.currentThread().getName() + " is end...");} catch (InterruptedException e) {e.printStackTrace();}}}}class B implements Runnable {private Object obj;public B(Object obj) {this.obj = obj;}@Overridepublic void run() {synchronized (obj) {try {System.out.println(Thread.currentThread().getName() + " is waiting...");obj.wait();System.out.println(Thread.currentThread().getName() + " is end...");} catch (InterruptedException e) {e.printStackTrace();}}}}  

運行:(1)首先a和b分別進入wait阻塞(2)2秒之後main線程調用notify通知了一個線程,a被喚醒執行(3)由於notify只能喚醒一個線程,所以b就永久阻塞了

可以看出:當第一個獲得了該對象鎖的wait線程運行完畢以後,它會釋放掉該對象鎖,此時如果該對象沒有再次使用notify語句,則即便該對象已經空閑,其他wait狀態等待的線程由於沒有得到該對象的通知,會繼續阻塞在wait狀態,直到這個對象發出一個notify或notifyAll。這裡需要注意:它們等待的是被notify或notifyAll,而不是鎖。這與下面的notifyAll()方法執行後的情況不同。 

(3)notify後,當前線程不會馬上釋放該對象鎖,wait所在的線程並不能馬上擷取該對象鎖,要等到notify所線上程退出synchronized代碼塊後,當前線程才會釋放鎖,wait所在的線程也才能可以擷取該對象鎖。

package com.huawei.thread;import java.util.Date;public class Test36 {public static void main(String[] args) throws InterruptedException {Object obj = new Object();Thread a = new Thread(new A(obj));Thread b = new Thread(new B(obj));a.setName("a");b.setName("b");a.start();b.start();Thread.sleep(2000);synchronized (obj) {obj.notifyAll();System.out.println(new Date());Thread.sleep(3000);}}}class A implements Runnable {private Object obj;public A(Object obj) {this.obj = obj;}@Overridepublic void run() {synchronized (obj) {try {System.out.println(Thread.currentThread().getName() + " is waiting...");obj.wait();System.out.println(Thread.currentThread().getName() + " is end at: " + new Date());} catch (InterruptedException e) {e.printStackTrace();}}}}class B implements Runnable {private Object obj;public B(Object obj) {this.obj = obj;}@Overridepublic void run() {synchronized (obj) {try {System.out.println(Thread.currentThread().getName() + " is waiting...");obj.wait();System.out.println(Thread.currentThread().getName() + " is end at: " + new Date());} catch (InterruptedException e) {e.printStackTrace();}}}}  

運行:可以看出主線程notifyAll之後會sleep(3000),然後才會釋放鎖,b線程也是在3秒之後才能擷取到鎖。

在實際編程中,我們應該盡量線上程調用notify/notifyAll後,立即退出臨界區釋放鎖,即不要在notify/notifyAll後面再寫一些耗時的代碼。

notifyAll

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

wait/notify模式

通常,多線程之間需要協調工作:如果條件不滿足,則等待;當條件滿足時,等待該條件的線程將被喚醒,在Java中,這個模式的實現依賴wait/notify。

synchronized(obj) {  while(!condition) {     obj.wait();  }  obj.doSomething();}  

當線程A獲得obj鎖後,發現條件condition不滿足,無法繼續下一步處理,於是線程A就wait;

另一個線程B中,如果B改改了某些條件,使得線程A的condition條件滿足,就可以喚醒線程A:

synchronized(obj) {  condition = true;  obj.notifyAll();} 
為什麼條件測試使用while,而不是if?

執行個體:有兩個線程從List中刪除資料,而只有一個線程向List中添加資料。初始時,List為空白,只有往List中添加了資料之後,才能刪除List中的資料。添加資料的線程向List添加完資料後,調用notifyAll(),喚醒了兩個刪除線程,但是它只添加了一個資料,而現在有兩個喚醒的刪除線程,這時如果使用if會怎樣?

結論:如果用 if 測試List中的資料的個數,則會出現IndexOutofBoundException。越界異常。原因是,List中只有一個資料,第一個刪除線程把資料刪除後,第二個線程再去執行刪除操作時,刪除失敗,從而拋出 IndexOutofBoundException。

 

 

 

另外:

(1)如果要把wait和notify放在一起使用的話,必須先調用notify方法,後調用wait。

(2)假設線程A執行wait,線程B執行notify:如果B先執行了notify然後結束,A執行wait阻塞,那此時A將無法被正常喚醒。(當然可以通過interrupt()方法以拋出異常的方式喚醒A)

 

wait與中斷

請參考《多線程-interrupt(),isInterrupted(),interrupted()》

  

小結

(1)如果線程調用了對象的wait方法,那麼線程便會處於該對象的等待池中,等待池中的線程不會去競爭該對象的鎖。

(2)當有線程調用了對象的notifyAll方法(喚醒所有wait線程)或notify方法(只隨機喚醒一個wait線程),被喚醒的的線程便會進入該對象的鎖池中,鎖池中的線程會去競爭該對象鎖。

(3)優先順序高的線程競爭到對象鎖的機率大,假若某線程沒有競爭到該對象鎖,它還會留在鎖池中

(4)唯有線程再次調用wait方法,它才會重新回到等待池中。而競爭到對象鎖的線程則繼續往下執行,直到執行完了synchronized代碼塊,它會釋放掉該對象鎖,這時鎖池中的線程會繼續競爭該對象鎖。

參考資料

http://blog.csdn.net/ns_code/article/details/17225469

http://blog.csdn.net/oracle_microsoft/article/details/6863662

http://www.cnblogs.com/hapjin/p/5492645.html

 

多線程-wait/notify/notifyAll

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.