notify()表现得像notifyAll()

时间:2017-03-01 12:33:05

标签: java multithreading

我有一个计算器线程计算从1到50的数字之和,以及一个计算器线程准备好后显示结果的多个Reader线程。我可以选择调用notify()和notifyAll()来通知Reader线程计算结果已准备好显示。在Calculator类的LINE B中,如果我调用notifyAll()方法,结果将按预期打印4次。但是当我用仅通知()替换LINE B时,我仍然看到打印4次的结果似乎不正确。据我所知,notify()只会唤醒其中一个正在等待的线程,而不是所有线程。当我调用notify时,为什么所有线程都会唤醒并打印结果?

public class ThreadWaitNotify {

    public static void main(String[] args) {
        Calculator c = new Calculator();
        Reader r = new Reader(c);
        Reader r2 = new Reader(c);
        Reader r3 = new Reader(c);
        Reader r4 = new Reader(c);

        r.start();
        r2.start();
        r3.start();
        r4.start();
        try {
            Thread.sleep(500L);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        c.start();
    }

}

读者课程:

class Reader extends Thread {

    Calculator c;

    public Reader(Calculator c) {
        this.c = c;
    }

    @Override
    public void run() {
        synchronized (c) {
            try {
                System.out.println(Thread.currentThread().getName() + " Waiting for calculations: ");
                c.wait();    // LINE A
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + " Total: " + c.getSum());
        }
    }
}

计算器类:

class Calculator extends Thread {

    private int sum = 0;

    @Override
    public void run() {
        synchronized (this) {
            for (int i = 1; i <= 50; i++) {
                sum += i;
            }
            notify();  // LINE B
        }
    }

    public int getSum() {
        return sum;
    }
}

输出:

Thread-1 Waiting for calculations: 
Thread-4 Waiting for calculations: 
Thread-3 Waiting for calculations: 
Thread-2 Waiting for calculations: 
Thread-1 Total: 1275
Thread-2 Total: 1275
Thread-3 Total: 1275
Thread-4 Total: 1275

======================

更新: 使用对象作为监视器/锁而不是Thread实例会产生正确的行为。

更新了ThreadWaitNotify类:

public class ThreadWaitNotify {

    public static void main(String[] args) {
        Object monitor = new Object();
        Calculator c = new Calculator(monitor);
        Reader r = new Reader(c, monitor);
        Reader r2 = new Reader(c, monitor);
        Reader r3 = new Reader(c, monitor);
        Reader r4 = new Reader(c, monitor);

        r.start();
        r2.start();
        r3.start();
        r4.start();
        try {
            Thread.sleep(500L);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        c.start();
    }

}

更新了读卡器类:

class Reader extends Thread {

    Calculator c;
    Object monitor;

    public Reader(Calculator c, Object monitor) {
        this.c = c;
        this.monitor = monitor;
    }

    @Override
    public void run() {
        synchronized (monitor) {
            try {
                System.out.println(Thread.currentThread().getName() + " Waiting for calculations: ");
                monitor.wait();   // LINE A
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + " Total: " + c.getSum());
        }
    }
}

更新的计算器类:

class Calculator extends Thread {

    private int sum = 0;
    Object monitor;

    public Calculator(Object monitor) {
        this.monitor = monitor;
    }

    @Override
    public void run() {
        synchronized (monitor) {
            for (int i = 1; i <= 50; i++) {
                sum += i;
            }
            monitor.notify();       // LINE B
        }
    }

    public int getSum() {
        return sum;
    }
}

5 个答案:

答案 0 :(得分:11)

notify()并不会唤醒所有读者Threads,而是Calculator's Thread's生命周期结束。

直到现在我还不知道这种行为,但看起来终止Thread总是会唤醒所有Threads等待它。只需在Thread.sleep()结尾处投入另一个Calculator.run(),您就会看到。

更新

我刚刚意识到阅读John's answer,这是一个至关重要的区别。

误解存在于等待它的短语中。 Thread确实会通知所有服务员,但它与Thread概念无关。

事实上,这是一种特殊的行为,特别是Thread,只要它到达生命的终点,就会通知所有等待{{1}的服务员。 } 对象本身。正如约翰已经指出的那样,这将在某个时刻发生,并且在#39; Thread,因此在JVM中,因此与对象释放无关。

结论

虽然这很可能是所描述错误的原因,但是永远不会依赖JVM内部发生的任何事情。事实证明,这种行为是一个明确的指标。

答案 1 :(得分:9)

我原来的回答是错误的。我刚刚浏览了本机代码,了解发生了什么。

当线程结束时,它实际上会通知退出线程监视器上的所有等待线程。同样,这是在本机级别,可以更改

thread.cpp - &gt;

void JavaThread::exit(bool destroy_vm, ExitType exit_type) {
      ..... other code ....
      // Notify waiters on thread object. This has to be done after exit() is called
      // on the thread (if the thread is the last thread in a daemon ThreadGroup the
      // group should have the destroyed bit set before waiters are notified).
      ensure_join(this);
      assert(!this->has_pending_exception(), "ensure_join should have cleared");
     .... other code ....

这是来自JDK 7的源代码,但我无法想象这个功能会有很大差异。

答案 2 :(得分:4)

除了其他人写的内容之外,我没有彻底分析代码,但是,就您的wait代码而言,您是否考虑了虚假唤醒?您的等待线程不仅可以通过拨打notify / notifyAll而且不确定地被唤醒。这就是为什么你应该总是在循环中调用wait,如下所示:

while (!condition) { obj.wait(); }

有关详细信息,请参阅this问题。

答案 3 :(得分:0)

Izruo的回答是对的。 我想补充一点。 如果你将sleep()放在synchronized块之外,那么其中一个线程将被唤醒而其他线程将处于等待状态。

答案 4 :(得分:-1)

调用notify不会启动任何正在运行的Reader线程。它所做的只是选择一个Reader并将其从Calculator的等待集中删除,这使得它能够在Calculator释放后获得计算器监视器 。计算器继续运行,离开同步块然后退出。

离开同步块后,所选的Reader开始运行,打印其总数,并离开同步块并退出。

此时没有人拥有显示器,因此理论上每个线程都被阻止。但Java语言规范允许JVM在没有任何人调用notify的情况下唤醒被阻塞的线程:&#34; The thread may be removed from the wait set due to ......实现的内部操作。允许实现(尽管不鼓励)执行“虚假唤醒”,即从等待集中删除线程,从而在没有明确指令的情况下启用恢复。&#34;它们不会开始运行(它们处于同步块中并且仍然必须获得锁定),但它们确实有资格运行。那一定是这里发生的事情。

有关此内容的更多信息:Coder Ranch threadDevGuli

它是一个导致唤醒的线程的理论可能是真的,但这只是因为它是虚假唤醒的诱发事件。