1. 程式人生 > >執行緒的掛起和喚醒

執行緒的掛起和喚醒

1. 執行緒的掛起和喚醒
      掛起實際上是讓執行緒進入“非可執行”狀態下,在這個狀態下CPU不會分給執行緒時間片,進入這個狀態可以用來暫停一個執行緒的執行;線上程掛起後,可以通過重新喚醒執行緒來使之恢復執行。

掛起的原因可能是如下幾種情況:
     (1)通過呼叫sleep()方法使執行緒進入休眠狀態,執行緒在指定時間內不會執行。
     (2)通過呼叫join()方法使執行緒掛起,使自己等待另一個執行緒的結果,直到另一個執行緒執行完畢為止。

     (3)通過呼叫wait()方法使執行緒掛起,直到執行緒得到了notify()和notifyAll()訊息,執行緒才會進入“可執行”狀態。
     (4)使用suspend掛起執行緒後,可以通過resume方法喚醒執行緒。
      雖然suspend和resume可以很方便地使執行緒掛起和喚醒,但由於使用這兩個方法可能會造成死鎖,因此,這兩個方法被標識為  deprecated(抗議)標記 ,這表明在以後的jdk版本中這兩個方法可能被刪除,所以儘量不要使用這兩個方法來操作執行緒。


      呼叫sleep()、yield()、suspend()的時候並沒有被釋放鎖

        yield() 使得執行緒放棄當前分得的 CPU 時間,但是不使執行緒阻塞,即執行緒仍處於可執行狀態,隨時可能再次分得 CPU 時間。呼叫 yield() 的效果等價於排程程式認為該執行緒已執行了足夠的時間從而轉到另一個執行緒

      呼叫wait()的時候釋放當前物件的鎖

      wait()方法表示,放棄當前對資源的佔有權,一直等到有執行緒通知,才會執行後面的程式碼。

      notify()方法表示,當前的執行緒已經放棄對資源的佔有,通知等待的執行緒來獲得對資源的佔有權,但是隻有一個執行緒能夠從wait狀態中恢復,然後繼續執行wait()後面的語句。

      notifyAll()方法表示,當前的執行緒已經放棄對資源的佔有,通知所有的等待執行緒從wait()方法後的語句開始執行。

       當第一個執行緒執行完畢以後釋放物件上的鎖此時如果該物件沒有再次使用notify語句,則即便該物件已經空閒,其他wait狀態等待的執行緒由於沒有得到該物件的通知,繼續處在wait狀態,直到這個物件發出一個notify或notifyAll,它們等待的是被notify或notifyAll,而不是鎖。 


2.
等待和鎖實現資源競爭

      等待機制與鎖機制是密切關聯的,對於需要競爭的資源,首先用synchronized確保這段程式碼只能一個執行緒執行,可以再設定一個標誌位condition判斷該資源是否準備好,如果沒有,則該執行緒釋放鎖,自己進入等待狀態,直到接收到notify,程式從wait處繼續向下執行。
  1. synchronized(obj) {
  2.   while(!condition) {
  3.    obj.wait();
  4.   }
  5.   obj.doSomething();
  6. }
以上程式表示只有一個執行緒A獲得了obj鎖後,發現條件condition不滿足,無法繼續下一處理,於是執行緒A釋放該鎖,進入wait()。

      在另一執行緒B中,如果B更改了某些條件,使得執行緒A的condition條件滿足了,就可以喚醒執行緒A:
  1. synchronized(obj) {
  2.  condition = true;
  3.  obj.notify();
  4. }
需要注意的是:
  # 呼叫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中的一個才有機會獲得鎖繼續執行。


例1:
單個執行緒對多個執行緒的喚醒

      假設只有一個Game物件,但有3個人要玩,由於只有一個遊戲資源,必須必然依次玩。
  1. /**
  2.  * 玩遊戲的人.
  3.  * @version V1.0 ,2011-4-8
  4.  * @author xiahui
  5.  */
  6. public class Player implements Runnable {
  7.     private final int id;
  8.     private Game game;

  9.     public Player(int id, Game game) {
  10.         this.id = id;
  11.         this.game = game;
  12.     }


  13.     public String toString() {
  14.         return "Athlete<" + id + ">";
  15.     }

  16.     public int hashCode() {
  17.         return new Integer(id).hashCode();
  18.     }
  19.     
  20.     public void playGame() throws InterruptedException{
  21.         System.out.println(this.toString() + " ready!");
  22.         game.play(this);
  23.     }

  24.     public void run() {
  25.         try {
  26.             playGame();
  27.         } catch (InterruptedException e) {
  28.             System.out.println(this + " quit the game");
  29.         }
  30.     }
  31. }
遊戲類,只例項化一個
  1. import java.util.HashSet;
  2. import java.util.Iterator;
  3. import java.util.Set;

  4. /**
  5.  * 遊戲類.
  6.  * @version V1.0 ,2011-4-8
  7.  * @author xiahui
  8.  */
  9. public class Game implements Runnable {
  10.     private boolean start = false;

  11.     public void play(Player player) throws InterruptedException {
  12.         synchronized (this) {
  13.             while (!start)
  14.                 wait();
  15.             if (start)
  16.                 System.out.println(player + " have played!");
  17.         }
  18.     }

  19.     //通知所有玩家
  20.     public synchronized void beginStart() {
  21.         start = true;
  22.         notifyAll();
  23.     }

  24.     public void run() {
  25.         start = false;
  26.         System.out.println("Ready......");
  27.         System.out.println("Ready......");
  28.         System.out.println("game start");
  29.         beginStart();//通知所有玩家遊戲準備好了
  30.     }

  31.     public static void main(String[] args) {
  32.         Set<Player> players = new HashSet<Player>();
  33.         //例項化一個遊戲
  34.         Game game = new Game();
  35.         
  36.         //例項化3個玩家
  37.         for (int i = 0; i < 3; i++)
  38.             players.add(new Player(i, game));
  39.         
  40.         //啟動3個玩家
  41.         Iterator<Player> iter = players.iterator();
  42.         while (iter.hasNext())
  43.             new Thread(iter.next()).start();
  44.         Thread.sleep(100);
  45.         
  46.         //遊戲啟動
  47.         new Thread(game).start();
  48.     }
  49. }
程式先啟動玩家,三個玩家競爭玩遊戲,但只能有一個進入play,其他二個等待,進入的玩家發現遊戲未準備好,所以wait,等遊戲準備好後,依次玩。
執行結果
  1. Athlete<0> ready!
  2. Athlete<1> ready!
  3. Athlete<2> ready!
  4. Ready......
  5. Ready......
  6. game start
  7. Athlete<2> have played!
  8. Athlete<1> have played!
  9. Athlete<0> have played!

3.一次喚醒一個執行緒
      一次喚醒所有玩家,但只有一個玩家能玩,不如一個一個喚醒
將上面的程式碼修改如下
  1.     public void play(Player player) throws InterruptedException {
  2.         synchronized (this) {
  3.             while (!start)
  4.                 wait();
  5.             if (start){
  6.                 System.out.println(player + " have played!");
  7.                 notify();//玩完後,通知下一個玩家來玩
  8.             }
  9.         }
  10.     }
  11.     //通知一個玩家
  12.     public synchronized void beginStart() {
  13.         start = true;
  14.         notify();
  15.     }

4.suspend掛起
      該方法已不建議使用,例子如下
例2:suspend方法進行掛起和喚醒
  1. /**
  2.  * suspend方法進行掛起和喚醒.
  3.  * @version V1.0 ,2011-3-27 
  4.  * @author xiahui
  5.  */
  6. public class SuspendThread implements Runnable{
  7.     public void run() {
  8.         try {
  9.             Thread.sleep(10);
  10.         } catch (Exception e) {
  11.             System.out.println(e);
  12.         }
  13.         for (int i = 0; i <= 1; i ) {
  14.             System.out.println(Thread.currentThread().getName() ":" i);
  15.         }
  16.     }

  17.     public static void main(String args[]) throws Exception {
  18.         Thread th1 = new Thread(new SuspendThread(),"thread1");
  19.         Thread th2 = new Thread(new SuspendThread(),"thread2");
  20.         System.out.println("Starting " th1.getName() "...");
  21.         th1.start();
  22.         System.out.println("Suspending " th1.getName() "...");
  23.         //Suspend the thread.
  24.         th1.suspend();
  25.         th2.start();
  26.         th2.join();
  27.         // Resume the thread.
  28.         th1.resume();
  29.     }
  30. }
執行結果
  1. Starting thread1...
  2. Suspending thread1...
  3. thread2:0
  4. thread2:1
  5. thread1:0
  6. thread1:1
注意:
      如果註釋掉//th2.join();則thread2執行後,主執行緒會直接執行thread1的resume,執行結果可能會是
  1. Starting thread1...
  2. Suspending thread1...
  3. thread1:0
  4. thread1:1
  5. thread2:0
  6. thread2:1

多執行緒爭取一個物件,某些情況下, 可以替代static靜態變數


import java.util.*;

class Widget...{}
class WidgetMaker extends Thread...{
    List<Widget> finishedWidgets=new ArrayList<Widget>();
    public void run()...{
        try...{
            while(true)...{
                Thread.sleep(5000);//act busy
                Widget w=new Widget();
                //也就是說需要5秒鐘才能新產生一個Widget,這決定了一定要用notify而不是notifyAll
                //因為上面兩行程式碼不是同步的,如果用notifyAll則所有執行緒都企圖衝出wait狀態
                //第一個執行緒得到了鎖,並取走了Widget(這個過程的時間小於5秒,新的Widget還沒有生成)
                //並且解開了鎖,然後第二個執行緒獲得鎖(因為用了notifyAll其他執行緒不再等待notify語句
                //,而是等待finishedWidgets上的鎖,一旦鎖放開了,他們就會競爭執行),執行
                //finishedWidgets.remove(0),但是由於finishedWidgets現在還是空的,
                //於是產生異常
                //***********這就是為什麼下面的那一句不能用notifyAll而是要用notify
                                
                synchronized(finishedWidgets)...{
                    finishedWidgets.add(w);
                    finishedWidgets.notify(); //這裡只能是notify而不能是notifyAll
                }
            }
        }
        catch(InterruptedException e)...{}
    }
    
    public Widget waitForWidget()...{
        synchronized(finishedWidgets)...{
            if(finishedWidgets.size()==0)...{
                try...{
                    finishedWidgets.wait();
                }
                catch(InterruptedException e)
                ...{}
            }
            return finishedWidgets.remove(0);
        }
    }
}
public class WidgetUser extends Thread...{
    private WidgetMaker maker;
    public WidgetUser(String name,WidgetMaker maker)...{
        super(name);
        this.maker=maker;
    }
    public void run()...{
        Widget w=maker.waitForWidget();
        System.out.println(getName()+"got a widget");
    }
   

    public static void main(String[] args) ...{
        WidgetMaker maker=new WidgetMaker();
        maker.start();
        new WidgetUser("Lenny",maker).start();
        new WidgetUser("Moe",maker).start();
        new WidgetUser("Curly",maker).start();

    }

}