2012-01-23 36 views
4

我想了解線程是如何工作的,我寫了一個簡單的例子,我想創建並啓動一個新線程,線程,在主線程中顯示從1到1000的數字,恢復輔助線程,並在輔助線程中顯示從1到1000的數字。當我忽略Thread.wait()/ Thread.notify()它的行爲如預期時,兩個線程一次顯示幾個數字。當我添加這些函數時,出於某種原因,主線程的數字將被打印第二個而不是第一個。我究竟做錯了什麼?獲取一個線程暫停 - Thread.wait()/ Thread.notify()

public class Main { 

    public class ExampleThread extends Thread { 

     public ExampleThread() { 
      System.out.println("ExampleThread's name is: " + this.getName()); 
     } 

     @Override 
     public void run() {   
      for(int i = 1; i < 1000; i++) { 
       System.out.println(Thread.currentThread().getName()); 
       System.out.println(i); 
      } 
     } 
    } 

    public static void main(String[] args) { 
     new Main().go(); 
    } 

    public void go() { 
     Thread t = new ExampleThread(); 
     t.start(); 

     synchronized(t) { 
      try { 
       t.wait(); 
      } catch (InterruptedException e) { 
       // TODO Auto-generated catch block 
       e.printStackTrace(); 
      } 
     } 

     for(int i = 1; i < 1000; i++) { 
      System.out.println(Thread.currentThread().getName()); 
      System.out.println(i); 
     } 

     synchronized(t) { 
      t.notify(); 
     } 
    } 
} 
+0

正如你所知,wait/notify被認爲已被棄用,你應該學習java.util.concurrent。 「Java併發實踐」是迄今爲止關於這個主題的最好的書。 – toto2

+0

@ toto2等待/通知不被視爲棄用。併發包包含更高級別的併發結構,通常*可以簡化併發編程,因此對於大多數情況下使用這些更高級別的結構是可取的。 –

回答

11

你誤解如何wait/notify作品。 wait確實不是阻止調用它的線程;它會阻止當前線程直到通知被稱爲上同一對象(所以如果你有線程A和B,而線程A,稱爲B.wait(),這將停止線程A和線程B - 只要不調用B.notify()。)。

因此,在您的具體示例中,如果您希望首先執行主線程,則需要將wait()放入輔助線程中。像這樣:

public class Main { 

    public class ExampleThread extends Thread { 

     public ExampleThread() { 
      System.out.println("ExampleThread's name is: " + this.getName()); 
     } 

     @Override 
     public void run() {   
      synchronized (this) { 
       try { 
        wait(); 
       } catch (InterruptedException e) { 
       } 
      } 
      for(int i = 1; i < 1000; i++) { 
       System.out.println(Thread.currentThread().getName()); 
       System.out.println(i); 
      } 
     } 
    } 

    public static void main(String[] args) { 
     new Main().go(); 
    } 

    public void go() { 
     Thread t = new ExampleThread(); 
     t.start(); 

     for(int i = 1; i < 1000; i++) { 
      System.out.println(Thread.currentThread().getName()); 
      System.out.println(i); 
     } 

     synchronized(t) { 
      t.notify(); 
     } 
    } 
} 

但是,即使此代碼可能無法正常工作。在主線程在之前到達notify()部分的情況下,輔助線程有機會到達wait()部分(在您的情況中不太可能,但仍然可能 - 您可以觀察它,如果將Thread放入。睡在輔助線程的開始處),輔助線程將永遠不會被喚醒。因此,最安全的方法可能與此類似:

public class Main { 

    public class ExampleThread extends Thread { 

     public ExampleThread() { 
      System.out.println("ExampleThread's name is: " + this.getName()); 
     } 

     @Override 
     public void run() { 
      synchronized (this) { 
       try { 
        notify(); 
        wait(); 
       } catch (InterruptedException e) { 
       } 
      } 
      for(int i = 1; i < 1000; i++) { 
       System.out.println(Thread.currentThread().getName()); 
       System.out.println(i); 
      } 
     } 
    } 

    public static void main(String[] args) { 
     new Main().go(); 
    } 

    public void go() { 
     Thread t = new ExampleThread(); 
     synchronized (t) { 
      t.start(); 
      try { 
       t.wait(); 
      } catch (InterruptedException e) { 
       // TODO Auto-generated catch block 
       e.printStackTrace(); 
      } 
     } 

     for(int i = 1; i < 1000; i++) { 
      System.out.println(Thread.currentThread().getName()); 
      System.out.println(i); 
     } 

     synchronized(t) { 
      t.notify(); 
     } 
    } 
} 

在此示例中,輸出是完全確定性的。這裏發生了什麼:

  1. 主線程創建一個新的t對象。
  2. 主線程在t監視器上鎖定。
  3. 主線程啓動t線程。
  4. (這些可以按任意順序發生)
    1. 輔助線程啓動,但由於主線程仍擁有t監控,輔助線程無法繼續,且必須等待(因爲它的第一條語句是synchronized (this),因爲它恰好t對象 - 所有的鎖,通知並等待可能也被物體完全無關的任何2個線程具有相同的結果對完成
    2. 主線程繼續,獲取到t.wait()部分,暫停執行,釋放t mon itor它同步。
  5. 輔助線程獲得t監視器的所有權。
  6. 輔助線程調用t.notify(),喚醒主線程。主線程不能繼續,因爲輔助線程仍然擁有t監視器的所有權。
  7. 輔助線程調用t.wait(),暫停執行並釋放t監視器。
  8. 主線程終於可以繼續,因爲t顯示器現在可用。
  9. 主線程獲得t監視器的所有權,但立即釋放它。
  10. 主線程執行其數字計數的事情。
  11. 主線程再次獲得t監視器的所有權。
  12. 主線程調用t.notify(),喚醒輔助線程。輔助線程不能繼續,因爲主線程仍然保持着t監視器。
  13. 主線程釋放t監視器並終止。
  14. 輔助線程獲得t監視器的所有權,但立即釋放它。
  15. 輔助線程執行它的數字計數事情,然後終止。
  16. 整個應用程序終止。

正如你所看到的,即使在這樣一個看似簡單的場景中,還有很多事情要做。

+0

如果線程t是一個類的成員變量,並且我在thread1中調用了t.wait(),然後在thread2中調用了t.wait(),會發生什麼?將調用t.notify()恢復兩個線程? – gsingh2011

+1

不,它只會恢復其中的一個(請參閱notify()的JavaDoc)。如果你想恢復兩者,你必須調用t.notifyAll(),或者調用t.notify()兩次。 – Seramme

2

ExampleThread不是wait()notify(),而不是​​任何東西。所以它會在沒有與其他線程協調的情況下運行。

主線程正在等待從未到達的通知(此通知應該由另一個線程發送)。我的猜測是,當ExampleThread死亡時,主線程被「虛假地」喚醒並完成。

應該等待另一個完成必須執行調用wait()一個循環,檢查一個條件內螺紋:

class ExampleThread extends Thread { 

    private boolean ready = false; 

    synchronized void ready() { 
    ready = true; 
    notifyAll(); 
    } 

    @Override 
    public void run() { 
    /* Wait to for readiness to be signaled. */ 
    synchronized (this) { 
     while (!ready) 
     try { 
      wait(); 
     } catch(InterruptedException ex) { 
      ex.printStackTrace(); 
      return; /* Interruption means abort. */ 
     } 
    } 
    /* Now do your work. */ 
    ... 

然後在你的主線程:

ExampleThread t = new ExampleThread(); 
t.start(); 
/* Do your work. */ 
... 
/* Then signal the other thread. */ 
t.ready(); 
2

你是幸運的是,你的程序終止。

當您致電t.wait()時,您的主線程將停止並無限期地等待通知。

它從來沒有得到它,但我相信被喚醒虛擬喚醒當輔助線程完成。 (閱讀here什麼是虛假喚醒)。