2016-04-03 42 views
1

我想弄清楚如何使用等待&通知,所以我寫了這個小例子,有幾架飛機在起飛前等待跑道清除,我遇到的問題是,當一架飛機起飛,並調用notifyAll(),只有一個線程似乎被喚醒,即我希望所有的線程報告他們已被通知,但仍在等待。實際發生的情況是,只有一個線程被喚醒,其餘的什麼都不會執行。爲什麼只有一個線程被喚醒,我該如何解決?爲什麼notifyAll()沒有喚醒這個例子中的所有線程?

class Plane extends Thread 
{ 
    Runway runway; 

    Plane(int id, Runway runway) 
    { 
     super(id + ""); 
     this.runway = runway; 

    } 

    public void run() 
    { 
     runway.taxi(); 
     runway.takeoff(); 
    } 
} 

class Runway 
{ 
    boolean isFull; 

    Runway() 
    { 
     isFull = false;; 
    } 

    public synchronized void taxi() 
    { 
     System.out.println(Thread.currentThread().getName() + " started to taxi"); 
     while(isFull) 
     { 
      System.out.println(Thread.currentThread().getName() + " is queued"); 
      try 
      { 
       wait(); 
      } 
      catch(InterruptedException e){}  
     } 
     isFull = true; 
     System.out.println(Thread.currentThread().getName() + " entering runway"); 
    } 

    public synchronized void takeoff() 
    { 
     try 
     { 
      Thread.currentThread().sleep(1000); 
     } 
     catch(InterruptedException e){} 
     System.out.println(Thread.currentThread().getName() + " took off"); 
     isFull = false; 
     notifyAll(); 
    } 

    public static void main(String[] args) 
    { 
     Runway runway = new Runway(); 
     new Plane(1, runway).start(); 
     new Plane(2, runway).start(); 
     new Plane(3, runway).start(); 
     new Plane(4, runway).start(); 
    } 
} 

感謝您抽出寶貴時間來幫助我:)

回答

1

假設您有4個飛機全部是start() - 一個接一個。

所有4將試圖跟隨taxi()通過takeoff()

第一個會叫taxi()打電話:

  • 獲取鎖,
  • 找到isFullfalse
  • 設置isFulltrue
  • 返回,釋放t他鎖定

然後其中一個(或多個)其餘線程可能會打電話給taxi()。如果他們這樣做,他們:

  • 獲取鎖
  • 找到isFullfalse
  • 呼叫wait()其解除鎖定

OR

  • 塊,而試圖收購鎖

與此同時,從taxi()返回的線程將調用takeoff()。這將:

  • 獲取鎖
  • 睡眠1秒,
  • 通知正在等待
  • 回報的線程解除鎖定。

那麼,這怎樣解釋你所看到的?

假設當第一個線程從taxi()返回時,它立即能夠重新獲取鎖並開始撥打takeoff()。然後它會撥打sleep() WHILE HOLDING THE LOCK。這將阻止任何其他線程啓動它們的調用(如果它們還沒有這樣做的話)。然後在睡覺之後,它會調用notifyAll()。但是,這隻會通知已進入taxi()調用的線程,並且已調用wait()。在啓動taxi()調用時被阻止的任何線程都不會看到通知。

(通知從不排隊不在wait()調用線程。)

這是可能的?嗯,是的。

啓動一個線程是一個相對昂貴/耗時的過程,並且很有可能第一個線程在下一個線程開始之前開始執行大量工作。在第二個嘗試呼叫taxi()之前,它有可能會一路通到sleep

對於其餘線程可能會重複相同的模式。當進入taxi()的每個線程很可能會在另一個線程被調度之前釋放並重新獲取它。 (線程調度是由操作系統來處理,它是優化效率,而非公平。如果你想公平調度,你需要使用一個Lock對象。)


...怎麼能修復它?

更改您的代碼,以便您在按住鎖定時不會sleep。例如:

public void takeoff() { 
    try { 
     Thread.currentThread().sleep(1000); 
    } catch (InterruptedException e) { 
     // squash ... 
    } 
    System.out.println(Thread.currentThread().getName() + " took off"); 
    synchronize (this) { 
     isFull = false; 
     notifyAll(); 
    } 
} 
3

因爲notifyAll的()不是wakeAll()。所有線程都會收到通知,但只有一個獲得密鑰並正在運行。所有其他人都等待再次拉動。

1

就是這樣。它「通知」所有等待的線程,但只有一個喚醒並獲取CPU。 notify()根據底層線程實現選擇的內容挑選等待線程。 notifyAll()爲所有等待的線程提供了相等的競爭機會。但無論哪種方式,只有一個線程採用上下文。

+0

實際上,OP的代碼寫入方式,其他線程沒有等待,所以他們從來沒有看到通知。看到我的答案進行詳細分析(和修復)。 –

相關問題