簡體   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