如何检测虚假唤醒
How to Detect a Spurious Wakeup
我看过很多关于这个的帖子。这个答案 通过建议测试中断标志获得了 100 赏金。
我测试了这个,它对我不起作用。所以,问题仍然存在,我如何检测虚假唤醒,或者这是不可能的?谢谢。
class TestSpuriousWakeup {
static Thread t1, tInterrupt, tNotify;
// spawn one thread that will be interrupted and be notified
// spawn one thread that will interrupt
// spawn one thread that will notify
public static void main(String[] args) {
System.out.println("*** main Starting");
initThreads();
try {
t1.start();
Thread.sleep(2000);
tNotify.start();
tNotify.join();
Thread.sleep(2000);
tInterrupt.start();
tInterrupt.join();
t1.join();
} catch (InterruptedException e) {
System.out.println("*** Unexpected interrupt in main");
}
System.out.println("*** main Ended.");
}
private static void initThreads() {
t1 = new Thread() {
@Override
public void run() {
System.out.println("ThreadInterruptMe Started ...");
boolean stop = false;
Thread.interrupted(); // clear the interrupted flag
while (!stop) {
try {
System.out.println("ThreadInterruptMe Sleeping 5000ms ...");
Thread.sleep(5000);
} catch (InterruptedException e) {
System.out.println("ThreadInterruptMe InterruptedException e!");
System.out.println("ThreadInterruptMe e.getCause => " + e.getCause());
System.out.println("ThreadInterruptMe e.getLocalizedMessage => " + e.getLocalizedMessage());
stop = Thread.interrupted();
if (stop) {
System.out.println("ThreadInterruptMe was INTERRUPTED because Thread.interrupted() is true"); // never happens
} else {
System.out.println("ThreadInterruptMe was NOTIFIED because Thread.interrupted() is false"); // always happens
}
} finally {
Thread.interrupted(); // clear the interrupted flag
System.out.println("ThreadInterruptMe InterruptedException finally");
}
}
System.out.println("ThreadInterruptMe Ended.");
}
};
tInterrupt = new Thread() {
@Override
public void run() {
System.out.println(" ThreadInterruptYou Started ... interrupting now!");
t1.interrupt();
System.out.println(" ThreadInterruptYou Ended.");
}
};
tNotify = new Thread() {
@Override
public void run() {
System.out.println(" ThreadNotifyYou Started ... notifying now!");
t1.interrupt();
System.out.println(" ThreadNotifyYou Ended.");
}
};
}
}
输出:
*** main Starting
ThreadInterruptMe Started ...
ThreadInterruptMe Sleeping 5000ms ...
ThreadNotifyYou Started ... notifying now!
ThreadInterruptMe InterruptedException e!
ThreadNotifyYou Ended.
ThreadInterruptMe e.getCause => null
ThreadInterruptMe e.getLocalizedMessage => sleep interrupted
ThreadInterruptMe was NOTIFIED because Thread.interrupted() is false
ThreadInterruptMe InterruptedException finally
ThreadInterruptMe Sleeping 5000ms ...
ThreadInterruptYou Started ... interrupting now!
ThreadInterruptMe InterruptedException e!
ThreadInterruptMe e.getCause => null
ThreadInterruptMe e.getLocalizedMessage => sleep interrupted
ThreadInterruptMe was NOTIFIED because Thread.interrupted() is false
ThreadInterruptMe InterruptedException finally
ThreadInterruptMe Sleeping 5000ms ...
ThreadInterruptYou Ended.
ThreadInterruptMe InterruptedException finally
ThreadInterruptMe Sleeping 5000ms ...
ThreadInterruptMe InterruptedException finally
ThreadInterruptMe Sleeping 5000ms ...
<infinite loop>
您通过重新测试谓词检测到虚假唤醒。
如果没有理由唤醒,则唤醒是虚假的。如果其他线程故意唤醒您,则唤醒不是虚假的。
或者,更简单地说:如果您等待的事情没有发生,则唤醒是虚假的。如果您等待的事情已经发生,则唤醒不是虚假的。你必须能够检查你正在等待或已经发生的事情是否已经发生——否则,你怎么知道你必须首先等待它?
因此,在线程有意唤醒您之前,让它设置一些您在唤醒时检查的同步变量。如果设置了该变量,则唤醒不是虚假的,您可以清除该标志。如果未设置,则唤醒是虚假的,您通常会忽略唤醒。
因此,流程如下:
至 wake/signal/interrupt 线程:
- 获取锁或进入同步块。
- 将布尔谓词设置为真。
- 唤醒线程。
- 释放锁或退出同步块。
(如果需要,可以调换 3 和 4 的顺序。)
等待某事发生:
- 获取锁或进入同步块。
- 将布尔谓词设置为 false(或检查它是否已设置)。
- 等待,释放锁。唤醒后,重新获取锁(或重新进入同步块)。
- 检查谓词以查看唤醒是否虚假。如果谓词为假,转到步骤 3。
- 你现在知道唤醒不是虚假的。释放锁或退出同步块。
什么是虚假唤醒不是
sleep()
不受虚假唤醒的影响。 它用于休眠的低级系统调用可能是,但 Java 会处理这个问题为您提供详细信息,如果过早唤醒,请重新进入系统调用。作为用户,您不会受到虚假唤醒的影响。
虚假唤醒也与线程中断无关。那是一个单独的工具。线程永远不会 "spuriously interrupted"。如果您的线程被打断,这意味着有人在您的线程中某个名为 Thread.interrupt()
的地方。找到那个代码,你就会找到罪魁祸首。
什么虚假唤醒是
如果您想测试虚假唤醒,运行 改为使用 Object.wait()
进行测试,因为这是受其影响的经典方法。
使用 wait()
的天真的方法是简单地调用它,期望它只会在其他线程调用 notify()
时 return。例如,消息发送循环可能是:
for (;;) {
synchronized (monitor) {
if (queue.isEmpty()) { // incorrect
monitor.wait();
}
}
send(queue.remove());
}
如果 wait()
在没有将消息添加到队列的情况下虚假地唤醒,这将失败。解决方法是在wait()
周围加一个循环,在每次线程被唤醒的时候验证条件。
for (;;) {
synchronized (monitor) {
while (queue.isEmpty()) { // correct
monitor.wait();
}
}
send(queue.remove());
}
然后,模拟虚假唤醒的最简单方法是简单地调用notify()
而不更改循环条件。
synchronized (monitor) {
monitor.notify();
}
这将对执行 wait()
的线程产生完全相同的效果,就好像它遇到虚假唤醒一样。使用 if
的错误代码不会意识到队列仍然是空的并且会崩溃。使用 while
的正确代码将重新检查条件并安全地重新输入 wait()
调用。
我看过很多关于这个的帖子。这个答案 通过建议测试中断标志获得了 100 赏金。
我测试了这个,它对我不起作用。所以,问题仍然存在,我如何检测虚假唤醒,或者这是不可能的?谢谢。
class TestSpuriousWakeup {
static Thread t1, tInterrupt, tNotify;
// spawn one thread that will be interrupted and be notified
// spawn one thread that will interrupt
// spawn one thread that will notify
public static void main(String[] args) {
System.out.println("*** main Starting");
initThreads();
try {
t1.start();
Thread.sleep(2000);
tNotify.start();
tNotify.join();
Thread.sleep(2000);
tInterrupt.start();
tInterrupt.join();
t1.join();
} catch (InterruptedException e) {
System.out.println("*** Unexpected interrupt in main");
}
System.out.println("*** main Ended.");
}
private static void initThreads() {
t1 = new Thread() {
@Override
public void run() {
System.out.println("ThreadInterruptMe Started ...");
boolean stop = false;
Thread.interrupted(); // clear the interrupted flag
while (!stop) {
try {
System.out.println("ThreadInterruptMe Sleeping 5000ms ...");
Thread.sleep(5000);
} catch (InterruptedException e) {
System.out.println("ThreadInterruptMe InterruptedException e!");
System.out.println("ThreadInterruptMe e.getCause => " + e.getCause());
System.out.println("ThreadInterruptMe e.getLocalizedMessage => " + e.getLocalizedMessage());
stop = Thread.interrupted();
if (stop) {
System.out.println("ThreadInterruptMe was INTERRUPTED because Thread.interrupted() is true"); // never happens
} else {
System.out.println("ThreadInterruptMe was NOTIFIED because Thread.interrupted() is false"); // always happens
}
} finally {
Thread.interrupted(); // clear the interrupted flag
System.out.println("ThreadInterruptMe InterruptedException finally");
}
}
System.out.println("ThreadInterruptMe Ended.");
}
};
tInterrupt = new Thread() {
@Override
public void run() {
System.out.println(" ThreadInterruptYou Started ... interrupting now!");
t1.interrupt();
System.out.println(" ThreadInterruptYou Ended.");
}
};
tNotify = new Thread() {
@Override
public void run() {
System.out.println(" ThreadNotifyYou Started ... notifying now!");
t1.interrupt();
System.out.println(" ThreadNotifyYou Ended.");
}
};
}
}
输出:
*** main Starting
ThreadInterruptMe Started ...
ThreadInterruptMe Sleeping 5000ms ...
ThreadNotifyYou Started ... notifying now!
ThreadInterruptMe InterruptedException e!
ThreadNotifyYou Ended.
ThreadInterruptMe e.getCause => null
ThreadInterruptMe e.getLocalizedMessage => sleep interrupted
ThreadInterruptMe was NOTIFIED because Thread.interrupted() is false
ThreadInterruptMe InterruptedException finally
ThreadInterruptMe Sleeping 5000ms ...
ThreadInterruptYou Started ... interrupting now!
ThreadInterruptMe InterruptedException e!
ThreadInterruptMe e.getCause => null
ThreadInterruptMe e.getLocalizedMessage => sleep interrupted
ThreadInterruptMe was NOTIFIED because Thread.interrupted() is false
ThreadInterruptMe InterruptedException finally
ThreadInterruptMe Sleeping 5000ms ...
ThreadInterruptYou Ended.
ThreadInterruptMe InterruptedException finally
ThreadInterruptMe Sleeping 5000ms ...
ThreadInterruptMe InterruptedException finally
ThreadInterruptMe Sleeping 5000ms ...
<infinite loop>
您通过重新测试谓词检测到虚假唤醒。
如果没有理由唤醒,则唤醒是虚假的。如果其他线程故意唤醒您,则唤醒不是虚假的。
或者,更简单地说:如果您等待的事情没有发生,则唤醒是虚假的。如果您等待的事情已经发生,则唤醒不是虚假的。你必须能够检查你正在等待或已经发生的事情是否已经发生——否则,你怎么知道你必须首先等待它?
因此,在线程有意唤醒您之前,让它设置一些您在唤醒时检查的同步变量。如果设置了该变量,则唤醒不是虚假的,您可以清除该标志。如果未设置,则唤醒是虚假的,您通常会忽略唤醒。
因此,流程如下:
至 wake/signal/interrupt 线程:
- 获取锁或进入同步块。
- 将布尔谓词设置为真。
- 唤醒线程。
- 释放锁或退出同步块。
(如果需要,可以调换 3 和 4 的顺序。)
等待某事发生:
- 获取锁或进入同步块。
- 将布尔谓词设置为 false(或检查它是否已设置)。
- 等待,释放锁。唤醒后,重新获取锁(或重新进入同步块)。
- 检查谓词以查看唤醒是否虚假。如果谓词为假,转到步骤 3。
- 你现在知道唤醒不是虚假的。释放锁或退出同步块。
什么是虚假唤醒不是
sleep()
不受虚假唤醒的影响。 它用于休眠的低级系统调用可能是,但 Java 会处理这个问题为您提供详细信息,如果过早唤醒,请重新进入系统调用。作为用户,您不会受到虚假唤醒的影响。
虚假唤醒也与线程中断无关。那是一个单独的工具。线程永远不会 "spuriously interrupted"。如果您的线程被打断,这意味着有人在您的线程中某个名为 Thread.interrupt()
的地方。找到那个代码,你就会找到罪魁祸首。
什么虚假唤醒是
如果您想测试虚假唤醒,运行 改为使用 Object.wait()
进行测试,因为这是受其影响的经典方法。
使用 wait()
的天真的方法是简单地调用它,期望它只会在其他线程调用 notify()
时 return。例如,消息发送循环可能是:
for (;;) {
synchronized (monitor) {
if (queue.isEmpty()) { // incorrect
monitor.wait();
}
}
send(queue.remove());
}
如果 wait()
在没有将消息添加到队列的情况下虚假地唤醒,这将失败。解决方法是在wait()
周围加一个循环,在每次线程被唤醒的时候验证条件。
for (;;) {
synchronized (monitor) {
while (queue.isEmpty()) { // correct
monitor.wait();
}
}
send(queue.remove());
}
然后,模拟虚假唤醒的最简单方法是简单地调用notify()
而不更改循环条件。
synchronized (monitor) {
monitor.notify();
}
这将对执行 wait()
的线程产生完全相同的效果,就好像它遇到虚假唤醒一样。使用 if
的错误代码不会意识到队列仍然是空的并且会崩溃。使用 while
的正确代码将重新检查条件并安全地重新输入 wait()
调用。