让线程暂停 – Thread.wait()/ Thread.notify()

我试图理解线程是如何工作的,我写了一个简单的例子,我想创建并启动一个新线程,线程,在主线程中显示1到1000的数字,恢复辅助线程,并显示次要线程中的数字从1到1000。 当我省略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(); } } } 

你误解了wait / notify工作原理。 wait不会阻塞调用它的线程; 它阻塞当前线程,直到在同一个对象上调用notify(所以如果你有线程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(); } } } 

但是,即使此代码可能无法正常工作。 在主线程有可能到达wait()部分之前到达notify()部分的情况下(在你的情况下不太可能,但仍然可能 - 你可以观察它,如果你把Thread.sleep置于在辅助线程的开头),辅助线程永远不会被唤醒。 因此,最安全的方法类似于:

 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监视器。
  5. 辅助线程获得t monitor的所有权。
  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. 整个应用程序终止。

正如您所看到的,即使在这样一个看似简单的场景中,也会发生很多事情。

你很幸运,你的程序终止了。

当你调用t.wait()你的主线程会停止并无限期地等待通知。

它永远不会得到它,但我相信当次要线程完成时, 虚假的唤醒会唤醒它。 (请阅读虚假唤醒的内容)。

ExampleThread没有wait()notify() ,并且没有在任何事情上synchronized 。 所以它会在没有任何与其他线程协调的情况下随时运行。

主线程正在等待从未发出的通知(此通知应由另一个线程发送)。 我的猜测是,当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();