简体   繁体   English

如何检测虚假唤醒

[英]How to Detect a Spurious Wakeup

I have read many posts on this. 我已经阅读了很多关于此的文章。 This answer https://stackoverflow.com/a/6701060/2359945 claimed a 100 bounty by suggesting to test the interrupted flag. 这个答案https://stackoverflow.com/a/6701060/2359945通过建议测试被中断的标志而获得了100的赏金。

I tested this, and it does not work for me. 我对此进行了测试,但它对我不起作用。 So, the question remains, how do I detect a spurious wakeup, or is it not possible? 因此,问题仍然存在,如何检测虚假唤醒,还是不可能? Thank you. 谢谢。

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.");
      }
    };
  }
}

Output: 输出:

*** 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>

What spurious wakeups are not 什么不是虚假的唤醒

sleep() is not subject to spurious wakeups. sleep()不受虚假唤醒的影响。 The low-level system call it uses to sleep may be, but Java takes care of this detail for you, re-entering the system call if it's woken up prematurely. 它用来睡眠的低级系统调用可能是,但是Java会为您处理此细节,如果过早唤醒它,请重新输入该系统调用。 As a user you are not exposed to spurious wakeups. 作为用户,您不会受到虚假的唤醒。

Spurious wakeups are also unrelated to thread interruption. 虚假唤醒也与线程中断无关。 That is a separate tool. 那是一个单独的工具。 Threads are never "spuriously interrupted". 线程永远不会“虚假地中断”。 If your thread is interrupted, that means somebody somewhere called Thread.interrupt() on your thread. 如果您的线程被中断,那意味着您的线程上有人叫Thread.interrupt() Find that code and you will have the culprit. 找到该代码,您将有罪魁祸首。

What spurious wakeups are 什么假唤醒

If you want to test spurious wakeups, run tests with Object.wait() instead, since that's the classic method that suffers from them. 如果要测试虚假唤醒,请改用Object.wait()运行测试,因为这是遭受它们困扰的经典方法。

The naïve way of using wait() is to simply call it, expecting that it will only return when some other thread calls notify() . 使用wait()的简单方法是简单地调用它,期望它仅在其他线程调用notify()时返回。 For example, a message sending loop might be: 例如,消息发送循环可能是:

for (;;) {
    synchronized (monitor) {
        if (queue.isEmpty()) {  // incorrect
            monitor.wait();
        }
    }

    send(queue.remove());
}

This will fail if wait() spuriously wakes up without a message having been added to the queue. 如果wait()在没有消息添加到队列的情况下虚假唤醒,则此操作将失败。 The solution is to add a loop around the wait() to verify the condition every time the thread is woken up. 解决方案是在每次唤醒线程时在wait()周围添加一个循环以验证条件。

for (;;) {
    synchronized (monitor) {
        while (queue.isEmpty()) {  // correct
            monitor.wait();
        }
    }

    send(queue.remove());
}

The simplest way to simulate a spurious wakeup, then, is to simply call notify() without changing the loop condition . 因此,模拟伪唤醒的最简单方法是简单地调用notify()而不更改循环条件

synchronized (monitor) {
    monitor.notify();
}

This will have the exact same effect on the thread performing wait() as if it encountered a spurious wakeup. 这将对执行wait()的线程产生完全相同的效果,就好像它遇到了虚假的唤醒一样。 The incorrect code using if won't realize the queue is still empty and will crash. 使用if的不正确代码不会意识到队列仍然为空,并且将崩溃。 The correct code using while will re-check the condition and safely re-enter the wait() call. 使用while的正确代码将重新检查条件,并安全地重新输入wait()调用。

You detect a spurious wakeup by re-testing the predicate. 您可以通过重新测试谓词来检测虚假唤醒。

A wakeup is spurious if there is no reason for the wakeup. 如果没有理由唤醒,则唤醒是虚假的。 A wakeup is not spurious if some other thread intentionally woke you. 如果其他某个线程有意唤醒了您,则唤醒并不是虚假的。

Or, to put it even more simply: If the thing you were waiting for hasn't happened, the wakeup was spurious. 或者,更简单地说:如果您等待的事情没有发生,则唤醒是虚假的。 If the thing you were waiting for has happened, the wakeup was not spurious. 如果您正在等待的事情发生了,则唤醒并不是虚假的。 You have to be able to check whether the thing you were waiting or has happened or not -- otherwise, how did you know you had to wait for it in the first place? 您必须能够检查正在等待的事情还是已经发生的事情-否则,您是如何知道首先要等待它的?

So before a thread intentionally wakes you, have it set some synchronized variable that you check when you wake up. 因此,在线程有意唤醒您之前,请它设置一些同步变量,以供您在唤醒时检查。 If that variable is set, then the wakeup was not spurious, and you clear the flag. 如果设置了该变量,则唤醒不是虚假的,您清除该标志。 If it is not set, then the wakeup was spuroius, and you typically ignore the wakeup. 如果未设置,则唤醒为spuroius,您通常会忽略唤醒。

So, the flow goes as follows: 因此,流程如下:

To wake/signal/interrupt at thread: 要在线程中唤醒/信号/中断:

  1. Acquire a lock or enter a synchronized block. 获取锁或输入同步块。
  2. Set the boolean predicate to true. 将布尔谓词设置为true。
  3. Wake the thread. 唤醒线程。
  4. Release the lock or exit the synchronized block. 释放锁或退出同步块。
    (If desired, the order of 3 and 4 can be swapped.) (如果需要,可以交换3和4的顺序。)

To wait for something to happen: 等待发生的事情:

  1. Acquire a lock or enter a synchronized block. 获取锁或输入同步块。
  2. Set the boolean predicate to false (or check it if it was already set). 将布尔谓词设置为false(或检查它是否已设置)。
  3. Wait, releasing the lock. 等待,释放锁。 When woken, reacquire the lock (or re-enter a synchronized block). 唤醒后,重新获取锁(或重新输入同步块)。
  4. Check the predicate to see if the wake is spurious. 检查谓词,以了解唤醒是否是虚假的。 If the predicate is false, go to step 3. 如果该谓词为假,请转到步骤3。
  5. You now know the wakeup was not spurious. 您现在知道唤醒不是虚假的。 Release the lock or exit the synchronized block. 释放锁或退出同步块。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM