Java多線程筆記三(線程通訊wait/notify/notifyAll/sleep/yield/join)__Java

來源:互聯網
上載者:User
一,wait()、notify()、notifyAll() 1,訊號量

線程間通訊的一個最簡單方式是在共用對象的變數裡設定訊號量。線程A在一個同步塊裡設定boolean型成員變數hasDataToProcess為true,線程B也在同步塊裡讀取hasDataToProcess這個成員變數。下面的例子使用了一個持有訊號的對象,並提供了set和check方法:

public class MySignal{  protected boolean hasDataToProcess = false;  public synchronized boolean hasDataToProcess(){    return this.hasDataToProcess;  }  public synchronized void setHasDataToProcess(boolean hasData){    this.hasDataToProcess = hasData;  }}

線程A和B必須獲得指向一個MySignal共用執行個體的引用,以便進行通訊。如果它們持有的引用指向不同的MySingal執行個體,那麼彼此將不能檢測到對方的訊號。

下邊看一個例子,準備處理資料的線程B正在等待資料變為可用。換句話說,它在等待線程A的一個訊號,這個訊號使hasDataToProcess()返回true。線程B運行在一個迴圈裡,以等待這個訊號:

protected MySignal sharedSignal = ......while(!sharedSignal.hasDataToProcess()){  //do nothing... busy waiting}

這種情況叫做忙等待,它沒有對運行等待線程的CPU進行有效利用,除非平均等待時間非常短,否則,讓等待線程進入睡眠或者非運行狀態更為明智,直到它接收到它等待的訊號。 2,等待與喚醒

Java有一個內建的等待機制來允許線程在等待訊號的時候變為非運行狀態。java.lang.Object 類定義了三個方法,wait()、notify()和notifyAll()來實現這個等待機制。
一個線程一旦調用了任意對象的wait()方法,就會變為waiting狀態,直到另一個線程調用了同一個對象的notify()方法(或者設定一個時間參數,逾時後會自動喚醒)。以下是MySingal的修改版本——使用了wait()和notify()的MyWaitNotify:

public class MonitorObject{}public class MyWaitNotify{  MonitorObject myMonitorObject = new MonitorObject();  public void doWait(){    synchronized(myMonitorObject){      try{        myMonitorObject.wait();      } catch(InterruptedException e){...}    }  }  public void doNotify(){    synchronized(myMonitorObject){      myMonitorObject.notify();    }  }}

調用doWait()使線程進入等待狀態,而喚醒等待線程將調用doNotify()。

如你所見,為了調用wait()或者notify(),線程必須先獲得那個對象的鎖。也就是說,線程必須在同步塊裡調用wait()或者notify()。這是強制性的。一個線程如果沒有持有對象鎖,將不能調用wait(),notify()或者notifyAll()。當你調用它們的時候,JVM首先要檢查下當前線程是否是鎖的擁有者,不是則拋出IllegalMonitorStateExcept異常。

注意一個問題:調用wait()和notify()都在同步塊中,且有相同的監視器對象,那麼對象調用一個方法時不會阻塞別的方法嗎。答案是不會。一旦線程調用了wait()方法,它就釋放了所持有的監視器對象上的鎖,進入waiting狀態直到其他線程調用這個對象上的notify()方法或者逾時。

notify()和notifyAll()都是Object對象用於通知處在等待該對象的線程的方法。區別在於:
notify會隨機喚醒一個wait狀態線程,並使它獲得該對象上的鎖,而不驚動其它線程,此時對於其它線程來說,它們等待的是notify訊號並一直等待下去。
notifyAll喚醒所有等待的線程,使它們的狀態從等待notify變成等待鎖,一旦該對象被解鎖,他們就會去競爭鎖,最終只有一個線程能獲得對象鎖並執行代碼,其它的線程依次執行。 3,丟失的訊號與假喚醒

當notify()和notifyAll()被調用時,有可能沒有線程處於等待狀態。也就是說,如果一個線程先於被通知線程調用wait()前調用了notify(),等待的線程將錯過這個訊號。這可能使等待線程永遠在等待,不再醒來。
為了避免訊號丟失, 用一個變數來儲存是否被通知過。在notify前,設定自己已經被通知過。在調用wait()前會檢查這個變數(沒被通知的話執行wait),在wait後設定自己沒有被通知過,需要等待通知。

由於莫名其妙的原因,線程有可能在沒有調用過notify()和notifyAll()的情況下醒來。這就是所謂的假喚醒(spurious wakeups)。
為了防止假喚醒,儲存訊號的成員變數將在一個while迴圈裡接受檢查,而不是在if運算式裡。這樣的一個while迴圈類似自旋鎖(如果長時間不調用doNotify方法,doWait方法會一直自旋,CPU會消耗太大)。被喚醒的線程會自旋直到自旋鎖(while迴圈)裡的條件變為false。以下代碼展示了這點:

public class MyWaitNotify3{  MonitorObject myMonitorObject = new MonitorObject();  boolean wasSignalled = false;  public void doWait(){    synchronized(myMonitorObject){      while(!wasSignalled){        try{          myMonitorObject.wait();         } catch(InterruptedException e){...}      }      //clear signal and continue running.      wasSignalled = false;    }  }  public void doNotify(){    synchronized(myMonitorObject){      wasSignalled = true;      myMonitorObject.notify();    }  }}
二,sleep()、yield()、join()

1,sleep()方法的作用是讓當前線程暫停指定的時間(毫秒)。需要注意的是其與wait方法的區別。最簡單的區別是,wait方法依賴於同步,而sleep方法可以直接調用。而更深層次的區別在於sleep方法只是暫時讓出CPU的執行權,並不釋放鎖。而wait方法則需要釋放鎖。
注意:sleep()是一個靜態方法。這意味著只對當前線程有效,一個常見的錯誤是調用t.sleep(),這裡的t是一個不同於當前線程的線程。即便是執行t.sleep (),也是當前線程進入睡眠,而不是t線程。Thread類的sleep()和yield()方法都是靜態,它們將在當前正在執行的線程上運行,所以在其他處於等待狀態的線程上調用這些方法是沒有意義的。

2,yield()方法的作用是暫停當前線程,以便其他線程有機會執行。yield不能指定暫停時間,並且也不能保證當前線程馬上停止(當前線程讓出後,可能又競爭到了鎖,然後繼續執行)。

3,join()方法的作用是父線程等待子線程執行完成後再執行,換句話說就是將並發的線程合并為順序執行的線程。如果在當前main線程中執行test.join(),那麼main會被阻塞,直到test完成後(類似插隊)。
join方法是通過wait方法實現的,如果join的線程還在執行,則將當前線程阻塞起來,直到join的線程執行完成,當前線程才能執行。不過有一點需要注意,這裡的join只調用了wait方法,卻沒有對應的notify方法,原因是Thread的start方法中做了相應的處理,所以當join的線程執行完成以後,會自動喚醒主線程繼續往下執行。

sleep()、yield()、join()三個方法作為線程通訊的操作,它們是位於Thread類中。但是,同樣實現線程通訊的wait()、notify()、notifyAll()方法卻不是位於Thread類中,而是位於Object類中。
因為這三個方法要操作鎖,而鎖是所有對象的一部分,這樣Java的每一個類都有用於線程間通訊的基本方法。我們只能在同步控制方法或同步控制塊中調用wait()、notify()、notifyAll()方法,不用考慮這個類是繼承了Thread類還是實現了Runnable介面。而sleep()方法可以在非同步控制方法中調用,因為它不用操作鎖。
實際上,在非同步控制方法中調用wait()、notify()、notifyAll()方法是可以通過編譯的,但運行時會報IllegalMonitorStateExcept異常。



參考自:並發編程網-線程通訊
Java 並發編程:線程間的協作(wait/notify/sleep/yield/join)

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在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.