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

时间:2013-02-17 18:37:19

标签: java multithreading notify

我知道本网站已经讨论过类似的问题,但考虑到一个具体的例子,我还没有得到进一步的帮助。我可以在理论上掌握关于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

计算的总和

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

5 个答案:

答案 0 :(得分:8)

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

  

...可能有理由使用notifyAll代替notify。就像将wait调用置于循环中一样,可防止对公共可访问对象发生意外或恶意通知,使用notifyAll代替notify可防止意外或恶意wait s由一个不相关的线程。这样的wait可以以其他方式“吞下”关键通知,让其预定的收件人无限期地等待。

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

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

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

  

总之,与wait提供的高级语言相比,直接使用notifyjava.util.concurrent就像使用“并发汇编语言”编程一样。 很少(如果有的话)在新代码中使用waitnotify如果您维护使用waitnotify的代码,确保它始终使用标准惯用法从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打印结果,其他的是正如理论和其他答案所表明的那样,永远等待。