我知道本网站已经讨论过类似的问题,但考虑到一个具体的例子,我还没有得到进一步的帮助。我可以在理论上掌握关于notifyAll()
“唤醒”的notify()和Thread
的区别,但是当使用它们中的任何一个而不是另一个时,我无法察觉它们如何影响程序的功能。因此,我设置以下代码,我想知道使用它们中的每一个有什么影响。我可以从一开始就说它们给出相同的输出(Sum打印3次)。
它们实际上有何不同?有人如何修改程序,以便应用通知或notifyAll
对其功能起到至关重要的作用(给出不同的结果)?
任务:
class MyWidget implements Runnable {
private List<Integer> list;
private int sum;
public MyWidget(List<Integer> 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<Integer> 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
计算的总和因此我的问题:有什么区别?
答案 0 :(得分:8)
这种差异比你的例子更加微妙。用Josh Bloch的话来说(Effective Java 2nd Ed,Item 69):
...可能有理由使用
notifyAll
代替notify
。就像将wait
调用置于循环中一样,可防止对公共可访问对象发生意外或恶意通知,使用notifyAll
代替notify
可防止意外或恶意wait
s由一个不相关的线程。这样的wait
可以以其他方式“吞下”关键通知,让其预定的收件人无限期地等待。
因此,我们的想法是,您必须考虑在您正在等待的同一台监视器上输入wait
的其他代码,以及其他线程吞噬通知而不按设计方式做出反应。
其他陷阱也适用,这可能导致线程不足,例如多个线程可能会等待不同的条件,但notify
总是会唤醒同一个线程,以及条件不满足的线程。
即使没有立即与你的问题相关,我也觉得引用这个结论很重要(原作者强调):
总之,与
wait
提供的高级语言相比,直接使用notify
和java.util.concurrent
就像使用“并发汇编语言”编程一样。 很少(如果有的话)在新代码中使用wait
和notify
。如果您维护使用wait
和notify
的代码,确保它始终使用标准惯用法从wait
循环内调用while
。通常应优先使用notifyAll
方法notify
。如果使用notify
,必须非常小心以确保活跃。
答案 1 :(得分:2)
这在各种文档中都很清楚。区别在于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个线程都不会被唤醒。
答案 2 :(得分:0)
notify
唤醒(任何)等待集中的一个线程,notifyAll
唤醒等待集中的所有线程。大部分时间都应该使用notifyAll
。如果您不确定使用哪个,请使用notifyAll
。
在某些情况下,等待完成后,所有等待的线程都可以执行有用的操作。一个例子是等待某个任务完成的一组线程;一旦任务完成,所有等待的线程都可以继续他们的业务。在这种情况下,您可以使用notifyAll()
同时唤醒所有等待的线程。
另一种情况,例如互斥锁定,只有一个等待线程在收到通知后可以做一些有用的事情(在这种情况下获取锁定)。在这种情况下,您宁愿使用notify()
。正确实现后,你可以在这种情况下使用notifyAll()
,但你会不必要地唤醒那些无论如何都无法做任何事情的线程。
notify
上的Javadocs。
notifyAll
上的Javadocs。
答案 3 :(得分:0)
只有一个线程等待总和不为零,没有区别。如果有多个线程在等待,则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 ();
}
}
}
答案 4 :(得分:0)
我发现我的程序发生了什么。即使使用Thread
,三个notify()
也会打印结果,因为它们无法进入等待状态。 widThread
中的计算执行得足够快,以便在等待状态下抢占其他Thread
的输入,因为它取决于条件mw.getSum() == 0
(while循环)。 widThread
计算总和,以便剩余的Thread
不会“看到”其值为0。
如果删除了while循环并且widThread
的开头是在其他Thread
开始之后,则notify()
只有一个Thread
打印结果,其他的是正如理论和其他答案所表明的那样,永远等待。