2012-01-23 39 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什么是虚假唤醒)。