4

我知道在这个网站上已经讨论过类似的问题,但是考虑到一个具体的例子,我还没有得到他们的帮助。我可以在理论上掌握 notify() 和notifyAll()关于Thread“唤醒”的区别,但是当使用其中任何一个而不是另一个时,我无法理解它们如何影响程序的功能。因此我设置了以下代码,我想知道使用它们中的每一个有什么影响。我可以从一开始就说它们给出了相同的输出(总和打印了 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,结果都是相同的: Sum 从 Thread 12 计算:27 Sum 从 Thread 11 计算:27 Sum 从 Thread 10 计算:27

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

4

5 回答 5

11

差异比您的示例所要引起的要微妙。用 Josh Bloch 的话(Effective Java 2nd Ed,Item 69):

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

所以这个想法是,您必须考虑wait在您等待的同一监视器上输入的其他代码片段,以及那些吞下通知而不以设计方式做出反应的其他线程。

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

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

总而言之,wait与. 很少有理由在新代码中使用和。如果您维护使用and的代码,请确保它始终使用标准习惯用法从循环中调用。该方法一般应优先使用。如果使用,必须非常小心以确保活性。notifyjava.util.concurrentwaitnotifywaitnotifywaitwhilenotifyAllnotifynotify

于 2013-02-17T18:43:13.217 回答
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 个线程都不会唤醒。

于 2013-02-17T18:45:37.140 回答
0

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

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

另一种情况,例如互斥锁,只有一个等待线程在收到通知后可以做一些有用的事情(在这种情况下获取锁)。在这种情况下,您宁愿使用notify(). 如果实施得当,您也可以notifyAll()这种情况下使用,但是您会不必要地唤醒无论如何不能做任何事情的线程。

Javadocs 上notify

Javadocs 上notifyAll

于 2013-02-17T18:44:48.437 回答
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 ();
        }
    }
}
于 2013-02-17T18:45:01.287 回答
0

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

于 2013-02-20T20:53:55.187 回答