简体   繁体   中英

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.

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

The naïve way of using wait() is to simply call it, expecting that it will only return when some other thread calls 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. The solution is to add a loop around the wait() to verify the condition every time the thread is woken up.

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 .

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

This will have the exact same effect on the thread performing wait() as if it encountered a spurious wakeup. The incorrect code using if won't realize the queue is still empty and will crash. The correct code using while will re-check the condition and safely re-enter the wait() call.

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.

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.
  3. Wake the thread.
  4. Release the lock or exit the synchronized block.
    (If desired, the order of 3 and 4 can be swapped.)

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).
  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.
  5. You now know the wakeup was not spurious. Release the lock or exit the synchronized block.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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