notify()和notifyAll()之间的区别

我知道在这个网站上已经讨论过类似的问题了,但考虑到一个具体的例子,我还没有得到进一步的帮助。 我可以理解在理论上关于Thread “唤醒”的notify()和notifyAll()的区别,但是当我们使用它们中的任何一个而不是另一个时,我无法察觉它们如何影响程序的function。 因此,我设置以下代码,我想知道使用它们中的每一个有什么影响。 我可以从一开始就说它们给出相同的输出(Sum打印3次)。

它们实际上有何不同? 如何修改程序,以便应用notify或notifyAll对其function起到至关重要的作用(给出不同的结果)?

任务:

 class MyWidget implements Runnable { private List list; private int sum; public MyWidget(List l) { list = l; } public synchronized int getSum() { return sum; } @Override public void run() { synchronized (this) { int total = 0; for (Integer i : list) total += i; sum = total; notifyAll(); } } 

}

线:

 public class MyClient extends Thread { MyWidget mw; public MyClient(MyWidget wid) { mw = wid; } public void run() { synchronized (mw) { while (mw.getSum() == 0) { try { mw.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("Sum calculated from Thread " + Thread.currentThread().getId() + " : " + mw.getSum()); } } public static void main(String[] args) { Integer[] array = { 4, 6, 3, 8, 6 }; List integers = Arrays.asList(array); MyWidget wid = new MyWidget(integers); Thread widThread = new Thread(wid); Thread t1 = new MyClient(wid); Thread t2 = new MyClient(wid); Thread t3 = new MyClient(wid); widThread.start(); t1.start(); t2.start(); t3.start(); } 

}

更新:我明确地写了它。 无论是使用notify还是notifyAll,结果都是相同的:从线程12计算的总和:27从线程11计算的总和:27从线程10计算的总和:27

因此我的问题是:有什么区别?

差异比你的例子所激发的更微妙。 用Josh Bloch的话来说(Effective Java 2nd Ed,Item 69):

…可能有理由使用notifyAll代替notify 。 就像将循环中的wait调用置于可公开访问的对象上的意外或恶意通知一样,使用notifyAll代替notify可以防止不相关的线程发生意外或恶意wait 。 否则,这样的wait可以“吞下”关键通知,使其预期接收者无限期地等待。

因此,我们的想法是,您必须考虑在您正在等待的同一台监视器上进入wait其他代码,以及其他线程吞噬通知而不以设计方式作出反应。

其他陷阱也适用,这可能导致线程饥饿,例如几个线程可能等待不同的条件,但notify总是发生唤醒同一个线程,以及条件不满足的线程。

即使没有立即与你的问题相关,我也觉得引用这个结论很重要(原作者强调):

总之,与java.util.concurrent提供的高级语言相比,直接使用waitnotify就像使用“并发汇编语言”编程一样。 如果有的话,很少有理由在新代码中使用waitnotify 如果您维护使用waitnotify代码, wait确保它始终使用标准惯用法在while循环内调用wait 。 通常应优先使用notifyAll方法来notify 。 如果使用notify ,必须非常小心以确保活跃。

这在各种文档中都很清楚。 区别在于notify()选择(随机)一个线程,等待给定的锁,然后启动它。 相反, notifyAll()重新启动等待锁定的所有线程。

最佳实践表明线程总是在循环中等待,仅在满足它们等待的条件时退出。 如果所有线程都这样做,那么你总是可以使用notifyAll() ,保证重新启动满足等待条件的每个线程。

编辑添加希望启发代码:

这个程序:

 import java.util.concurrent.CountDownLatch; public class NotifyExample { static final int N_THREADS = 10; static final char[] lock = new char[0]; static final CountDownLatch latch = new CountDownLatch(N_THREADS); public static void main(String[] args) { for (int i = 0; i < N_THREADS; i++) { final int id = i; new Thread() { @Override public void run() { synchronized (lock) { System.out.println("waiting: " + id); latch.countDown(); try { lock.wait(); } catch (InterruptedException e) { System.out.println("interrupted: " + id); } System.out.println("awake: " + id); } } }.start(); } try { latch.await(); } catch (InterruptedException e) { System.out.println("latch interrupted"); } synchronized (lock) { lock.notify(); } } } 

生成此输出,在一个示例运行中:

 waiting: 0 waiting: 4 waiting: 3 waiting: 6 waiting: 2 waiting: 1 waiting: 7 waiting: 5 waiting: 8 waiting: 9 awake: 0 

除非有进一步的通知要求,否则其他9个线程都不会被唤醒。

notify唤醒(任何)等待集中的一个线程, notifyAll唤醒等待集中的所有线程。 应该在大多数时间使用notifyAll 。 如果您不确定使用哪个,请使用notifyAll

在某些情况下,等待完成后,所有等待的线程都可以执行有用的操作。 一个例子是等待某个任务完成的一组线程; 一旦任务完成,所有等待的线程都可以继续他们的业务。 在这种情况下,您将使用notifyAll()同时唤醒所有等待的线程。

另一种情况,例如互斥锁定,只有一个等待线程在被通知后可以做一些有用的事情(在这种情况下获取锁)。 在这种情况下,您宁愿使用notify() 。 正确实现后,您也可以在这种情况下使用notifyAll() ,但是无论如何都会不必要地唤醒无法执行任何操作的线程。

Javadocs notify

关于notifyAll Javadocs。

只有一个线程等待总和不为零,没有区别。 如果有多个线程在等待,则notify只会唤醒其中一个,而其他所有线程将永远等待。

运行此测试以更好地了解差异:

 public class NotifyTest implements Runnable { @Override public void run () { synchronized (NotifyTest.class) { System.out.println ("Waiting: " + this); try { NotifyTest.class.wait (); } catch (InterruptedException ex) { return; } System.out.println ("Notified: " + this); } } public static void main (String [] args) throws Exception { for (int i = 0; i < 10; i++) new Thread (new NotifyTest ()).start (); Thread.sleep (1000L); // Let them go into wait () System.out.println ("Doing notify ()"); synchronized (NotifyTest.class) { NotifyTest.class.notify (); } Thread.sleep (1000L); // Let them print their messages System.out.println ("Doing notifyAll ()"); synchronized (NotifyTest.class) { NotifyTest.class.notifyAll (); } } } 

我发现我的程序发生了什么事。 即使使用notify() ,三个Thread也会打印结果,因为它们无法进入等待状态。 widThread的计算执行得足够快,以便在等待状态下抢占其他Thread的输入,因为它取决于条件mw.getSum() == 0 (while循环)。 widThread计算总和,以便剩余的Thread不会“看到”它的值为0.如果删除了while循环并且widThread的开始在其他Thread的启动之后,那么仅通过notify()理论和其他答案表明,一个Thread打印结果,其他Thread永远等待。