繁体   English   中英

如何检测虚假唤醒

[英]How to Detect a Spurious Wakeup

我已经阅读了很多关于此的文章。 这个答案https://stackoverflow.com/a/6701060/2359945通过建议测试被中断的标志而获得了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>

什么不是虚假的唤醒

sleep()不受虚假唤醒的影响。 它用来睡眠的低级系统调用可能是,但是Java会为您处理此细节,如果过早唤醒它,请重新输入该系统调用。 作为用户,您不会受到虚假的唤醒。

虚假唤醒也与线程中断无关。 那是一个单独的工具。 线程永远不会“虚假地中断”。 如果您的线程被中断,那意味着您的线程上有人叫Thread.interrupt() 找到该代码,您将有罪魁祸首。

什么假唤醒

如果要测试虚假唤醒,请改用Object.wait()运行测试,因为这是遭受它们困扰的经典方法。

使用wait()的简单方法是简单地调用它,期望它仅在其他线程调用notify()时返回。 例如,消息发送循环可能是:

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()调用。

您可以通过重新测试谓词来检测虚假唤醒。

如果没有理由唤醒,则唤醒是虚假的。 如果其他某个线程有意唤醒了您,则唤醒并不是虚假的。

或者,更简单地说:如果您等待的事情没有发生,则唤醒是虚假的。 如果您正在等待的事情发生了,则唤醒并不是虚假的。 您必须能够检查正在等待的事情还是已经发生的事情-否则,您是如何知道首先要等待它的?

因此,在线程有意唤醒您之前,请它设置一些同步变量,以供您在唤醒时检查。 如果设置了该变量,则唤醒不是虚假的,您清除该标志。 如果未设置,则唤醒为spuroius,您通常会忽略唤醒。

因此,流程如下:

要在线程中唤醒/信号/中断:

  1. 获取锁或输入同步块。
  2. 将布尔谓词设置为true。
  3. 唤醒线程。
  4. 释放锁或退出同步块。
    (如果需要,可以交换3和4的顺序。)

等待发生的事情:

  1. 获取锁或输入同步块。
  2. 将布尔谓词设置为false(或检查它是否已设置)。
  3. 等待,释放锁。 唤醒后,重新获取锁(或重新输入同步块)。
  4. 检查谓词,以了解唤醒是否是虚假的。 如果该谓词为假,请转到步骤3。
  5. 您现在知道唤醒不是虚假的。 释放锁或退出同步块。

暂无
暂无

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

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