我有一个计算器线程计算从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;
}
}
答案 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 thread,DevGuli
它是一个导致唤醒的线程的理论可能是真的,但这只是因为它是虚假唤醒的诱发事件。