让线程暂停 – 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(); } } }
在此示例中,输出完全是确定性的。 这是发生的事情:
- 主线程创建一个新的
t
对象。 - 主线程在
t
监视器上获得锁定。 - 主线程启动
t
线程。 - (这些可以按任何顺序发生)
- 辅助线程启动,但由于主线程仍然拥有
t
监视器,辅助线程无法继续并且必须等待(因为它的第一个语句是synchronized (this)
, 而不是因为它碰巧是t
对象 - 所有锁,通知和等待也可以在与具有相同结果的2个线程中的任何一个完全无关的对象上完成。 - 主线程继续,到达
t.wait()
部分并暂停其执行,释放它同步的t
监视器。
- 辅助线程启动,但由于主线程仍然拥有
- 辅助线程获得
t
monitor的所有权。 - 辅助线程调用
t.notify()
,唤醒主线程。 但是主线程还不能继续,因为辅助线程仍然拥有t
监视器的所有权。 - 辅助线程调用
t.wait()
,暂停其执行并释放t
监视器。 - 主线程最终可以继续,因为
t
监视器现在可用。 - 主线程获得
t
监视器的所有权,但立即释放它。 - 主线程进行数字计数。
- 主线程再次获得
t
监视器的所有权。 - 主线程调用
t.notify()
,唤醒辅助线程。 辅助线程还不能继续,因为主线程仍然保持t
监视器。 - 主线程释放
t
监视器并终止。 - 辅助线程获得
t
监视器的所有权,但立即释放它。 - 辅助线程进行数字计数,然后终止。
- 整个应用程序终止。
正如您所看到的,即使在这样一个看似简单的场景中,也会发生很多事情。
你很幸运,你的程序终止了。
当你调用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();