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
提供的高级语言相比,直接使用wait
和notify
就像使用“并发汇编语言”编程一样。 如果有的话,很少有理由在新代码中使用wait
和notify
。 如果您维护使用wait
和notify
代码,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
永远等待。