線程中的wait和notify方法


synchronize 保證了多線程並發時
線程間的互斥行
代碼塊的原子性
變量的可見性
但是沒有提供方法實現線程間的同步通信機制
而wait(),notify()和notifyall()方法是java.lang.Object類為線程提供的用於實現線程間通信的同步控制方法
 
通常,多線程之間需要協調工作。例如,瀏覽器的一個顯示圖片的線程displayThread想要執行顯示圖片的任務,必須等待下載線程 downloadThread將該圖片下載完畢。
如果圖片還沒有下載完,displayThread可以暫停,當downloadThread完成了任務 后,再通知displayThread“圖片准備完畢,可以顯示了”,這時,displayThread繼續執行。
以上邏輯簡單的說就是:如果條件不滿足,則等待。當條件滿足時,等待該條件的線程將被喚醒。在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.notify();


需要注意的概念是:

  • 調用obj的wait(), notify()方法前,必須獲得obj鎖,也就是必須寫在synchronized(obj) {...} 代碼段內。
  • 調用obj.wait()后,線程A就釋放了obj的鎖,否則線程B無法獲得obj鎖,也就無法在synchronized(obj) {...} 代碼段內喚醒A。
  • 當obj.wait()方法返回后,線程A需要再次獲得obj鎖,才能繼續執行。
  • 如果A1,A2,A3都在obj.wait(),則B調用obj.notify()只能喚醒A1,A2,A3中的一個(具體哪一個由JVM決定)。
  • obj.notifyAll()則能全部喚醒A1,A2,A3,但是要繼續執行obj.wait()的下一條語句,必須獲得obj鎖,因此,A1,A2,A3只有一個有機會獲得鎖繼續執行,例如A1,其余的需要等待A1釋放obj鎖之后才能繼續執行。
  • 當B調用obj.notify/notifyAll的時候,B正持有obj鎖,因此,A1,A2,A3雖被喚醒,但是仍無法獲得obj鎖。直到B退出synchronized塊,釋放obj鎖后,A1,A2,A3中的一個才有機會獲得鎖繼續執行。
 
wait()方法使當前線程主動釋放互斥鎖,並進入該互斥鎖的等待隊列。
(也就是說,它使當前線程暫停執行, 等待其他線程執行notify()方法或者notifyall()方法后再競爭該鎖,如果成功就繼續執行本線程,否則等待下次喚醒)
 
notify()系列方法,用於釋放一個項目的線程,喚醒另一個可能在等待的線程.
 
線程等待有兩種調用格式:
1. wait()等待通信線程喚醒后再繼續執行本線程,沒喚醒的時候處於sleeping狀態,不會被分布CPU,也不會主動的競爭鎖.
2. wait(long millis)等待通信線程喚醒或者最多等待millis毫秒后,自動喚醒,然后嘗試競爭對象的鎖,如果不成功就進步blocking狀態.

調用wait()和notify()系列方法時,當前線程必須擁有此對象監視器(即對象鎖)。如果當前線程不是此對象監視器的所有者,會拋IllegalMonitorStateException。
通過以下三種方法之一,線程可以成為對象監視器的所有者:
    * 通過執行此對象的同步實例方法。
    * 通過執行在此對象上進行同步的 synchronized 語句的正文。
    * 對於 Class 類型的對象,可以通過執行該類的同步靜態方法。 
 
注意1:對於一個對象,某一時刻只能有一個線程擁有該對象的監視器。 

public final void wait() throws InterruptedException

    在其他線程調用此對象的 notify() 方法或 notifyAll() 方法前,導致當前線程等待。換句話說,此方法的行為就好像它僅執行 wait(0) 調用一樣。
    當前線程必須擁有此對象監視器。該線程發布對此監視器的所有權並等待,直到其他線程通過調用 notify 方法,
    或 notifyAll 方法通知在此對象的監視器上等待的線程醒來。然后該線程將等到重新獲得對監視器的所有權后才能繼續執行。
    對於某一個參數的版本,實現中斷和虛假喚醒是可能的,而且此方法應始終在循環中使用:

    synchronized (obj) {
    while (<condition does not hold>)
    obj.wait();
    ... // Perform action appropriate to condition
         }

    此方法只應由作為此對象監視器的所有者的線程來調用。
    拋出:
        IllegalMonitorStateException - 如果當前線程不是此對象監視器的所有者。 
        InterruptedException - 如果在當前線程等待通知之前或者正在等待通知時,任何線程中斷了當前線程。
        在拋出此異常時,當前線程的中斷狀態 被清除。
注意1:
wait()方法后,當前線程暫停執行,線程進入sleep狀態,cpu不會分給其時間,等待其他線程執行notify()方法或者notifyall()方法或用interrupt()取消后再繼續執行本線程
它還有種調用格式:wait(long millis)等待通信線程喚醒或者最多等待millis毫秒后,再繼續執行本線程。
    
注意2:wait()方法使當前線程主動釋放互斥鎖.
注意3:線程A調用了wait()進入了等待狀態,也可以用interrupt()取消.詳細參考《線程的interrupt》
注意4:對於某一個參數的版本,實現中斷和虛假喚醒是可能的,而且此方法應始終在循環中使用。

public final void notify()
    喚醒在此對象監視器上等待的單個線程。如果所有線程都在此對象上等待,則會選擇喚醒其中一個線程。
    選擇是任意性的,並在對實現做出決定時發生。線程通過調用其中一個 wait 方法,在對象的監視器上等待。
    直到當前線程放棄此對象上的鎖定(也就是完成synchronized 代碼塊),才能繼續執行被喚醒的線程。被喚醒的線程將以常規方式與在該對象上主動同步的其他所有線程進行競爭;
    例如,喚醒的線程在作為鎖定此對象的下一個線程方面沒有可靠的特權或劣勢。 

注意1:
被notify()喚醒的線程,也只有在當前線程結束Synchronized代碼塊,放棄此對象上的鎖定后,才可能被執行.
它需要以常規方式與在該對象上主動synchronized的線程進行鎖競爭,競爭成功了還能被執行.

public final void notifyAll()
    喚醒在此對象監視器上等待的所有線程。線程通過調用其中一個 wait 方法,在對象的監視器上等待。
    直到當前線程放棄此對象上的鎖定,才能繼續執行被喚醒的線程。被喚醒的線程將以常規方式與在該對象上主動同步的其他所有線程進行競爭;
    例如,喚醒的線程在作為鎖定此對象的下一個線程方面沒有可靠的特權或劣勢。
    此方法只應由作為此對象監視器的所有者的線程來調用。
    拋出:IllegalMonitorStateException - 如果當前線程不是此對象監視器的所有者。
       
注意1:
被notify()喚醒的線程,也只有在當前線程介紹Synchronized代碼塊,放棄此對象上的鎖定后,才可能被執行.
它需要以常規方式與在該對象上主動synchronized的線程進行鎖競爭,競爭成功了還能被執行.

wait(),notify(),notifyall()所在對象和鎖是綁定的,它們是一一對應的,因此它的wait(),notify(),notifyall()都是操作同一個對象.
但是我們只想通過notify(),notifyall()來對一些wait()線程而不是該鎖上所有wait()的線程進行喚醒時。(這樣的效率要高很多,當然應該不只是可以得到效率的好處)
synchronized和wait(),notify(),notifyall()就顯得蒼白無力。 幸好java1.5中提供了ReentrantLock,ReadLock,WriteLock來和Condition配套使用以實現這種功能。

示例1
class BoundedBuffer {
       final String[] items = new String[100];
       int putptr, takeptr, count;
       final String conditionPut="put";
       final String conditionTake="take";
       synchronized public void put(String x) throws InterruptedException {
           while (count == items.length)
           {
               System.out.println("It is full.the action of put is wait");
               synchronized(conditionPut)
               {
                   conditionPut.wait();//@1
               }
           }
           items[putptr] = x;
           if (++putptr == items.length) putptr = 0;
           ++count;
           synchronized(conditionTake)
           {
               conditionTake.notify();
           }
       }

       synchronized public String take() throws InterruptedException {
           while (count == 0)
           {
               System.out.println("It is empty.the action of take is wait");
               synchronized(conditionTake)
               {
                   conditionTake.wait();//@1
               }
           }
           String x = items[takeptr];
           if (++takeptr == items.length) takeptr = 0;
           --count;
           synchronized(conditionPut)
           {
               conditionPut.notify();
           }
           return x;
       }
     }
注意:
在@1處無法進行當前線程對this的鎖的釋放,當然別的線程也就無法進入put和take,線程就死鎖了。
 
實例1:
因為wait(),notify(),notifyall()所在對象和鎖是綁定的notify(),notifyall()是對對象(鎖)上所有wait()的線程進行喚醒(notify是隨機選一個)
所以只能用下面的方式。
class BoundedBuffer {
       final String[] items = new String[100];
       int putptr, takeptr, count;
       synchronized public void put(String x) throws InterruptedException {
           while (count == items.length)@5
           {
               System.out.println("It is full.the action of put is wait");
               this.wait();//@1
           }
           items[putptr] = x;
           if (++putptr == items.length) 
           {
               putptr = 0;
            }
           ++count;
           this.notify();@2
       }

       synchronized public String take() throws InterruptedException {
           while (count == 0)//@6
           {
               System.out.println("It is empty.the action of take is wait");
               this.wait();//@3
           }
           String x = items[takeptr];
           if (++takeptr == items.length) 
           {
                   takeptr = 0;
           }
           --count;
           this.notify();//@4
           return x;
       }
     }
注意:
雖然我們希望的是@1處由@4出來喚醒,@3處由@2出來喚醒。但是notify(),notifyall()是對鎖上所有wait()的線程進行喚醒(notify是隨機選一個)。
因此@2出也可能喚醒@1,所以@5出要用while來判斷是否真的滿足條件了,@6同理。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM