Forcing a spurious-wake up in Java

后端 未结 2 644
别跟我提以往
别跟我提以往 2021-02-11 19:03

This question is not about, whether spurious wakeups actually happen, because this was already discussed in full length here: Do spurious wakeups in Java actually happen? Theref

相关标签:
2条回答
  • 2021-02-11 19:52

    You can't force a spurious wakeup, but to the running thread, a spurious wakeup is indistinguishable from a regular wakeup (the source of the event is different, but the event itself is the same)

    To simulate a spurious wakeup, simply call notify();

    Calling interrupt() isn't suitable, because doing so sets the interrupt flag, and after a spurious wakeup, the interrupt flag is not set

    0 讨论(0)
  • 2021-02-11 19:55

    "Spurious wakeup" is a hotchpotch and covers any implementation detail in that realm. Therefore it is quite hard to make out what a "real" spurious wakeup is and why another one is "unreal" - let alone on which layer this implementation detail originates. Choose any one from "kernel", "system library (libc)", "JVM", "Java standart library (rt.jar)" or a custom framework built on top of this stack.

    The following program shows a spurious wakeup using java.util.concurrent stuff:

    import java.util.concurrent.locks.Condition;
    import java.util.concurrent.locks.Lock;
    import java.util.concurrent.locks.ReentrantLock;
    
    public class SpuriousWakeupRWLock {
        static Lock lock = new ReentrantLock();
        static Condition condition = lock.newCondition();
        static int itemsReady;
    
        public static void main(String[] args) throws Exception {
    
            // let consumer 1 enter condition wait
            new ConsumerOne().start();
            Thread.sleep(500);
    
            lock.lock();
            try {
                // let consumer 2 hit the lock
                new ConsumerTwo().start();
                Thread.sleep(500);
    
                // make condition true and signal one (!) consumer
                System.out.println("Producer: fill queue");
                itemsReady = 1;
                condition.signal();
                Thread.sleep(500);
            }
            finally {
                // release lock
                lock.unlock();
            } 
    
            System.out.println("Producer: released lock");
            Thread.sleep(500);
        }
    
        abstract static class AbstractConsumer extends Thread {
            @Override
            public void run() {
                lock.lock();
                try {
                    consume();
                } catch(Exception e){
                    e.printStackTrace();
                } finally {
                    lock.unlock();
                }
            }
            abstract void consume() throws Exception;
        }
    
        static class ConsumerOne extends AbstractConsumer {
            @Override
            public void consume() throws InterruptedException {
                if( itemsReady <= 0 ){      // usually this is "while"
                    System.out.println("One: Waiting...");
                    condition.await();
                    if( itemsReady <= 0 )
                        System.out.println("One: Spurious Wakeup! Condition NOT true!");
                    else {
                        System.out.println("One: Wakeup! Let's work!");
                        --itemsReady;
                    }
                }
            }
        }
    
        static class ConsumerTwo extends AbstractConsumer {
            @Override
            public void consume() {
                if( itemsReady <= 0 )
                    System.out.println("Two: Got lock, but no work!");
                else {
                    System.out.println("Two: Got lock and immediatly start working!");
                    --itemsReady;
                }
            }
        }
    }
    

    Output :

    One: Waiting...
    Producer: fill queue
    Producer: released lock
    Two: Got lock and immediatly start working!
    One: Spurious Wakeup! Condition NOT true!
    

    The used JDK was:

    java version "1.6.0_20"
    OpenJDK Runtime Environment (IcedTea6 1.9.9) (6b20-1.9.9-0ubuntu1~10.04.2)
    OpenJDK 64-Bit Server VM (build 19.0-b09, mixed mode)
    

    It is based on one implementation detail in java.util.concurrent: The standard Lock has one waiting queue, the Condition has another waiting queue. If the condition is signalled, the signalled thread is moved from the condition's queue into the lock's queue. The implementation detail: It is moved at the end of the queue. If another thread is already waiting in the lock queue and this second thread did not visit the condition variable, this thread can "steal" the signal. If the implementation would have put the first thread before the second thread, this would not have happened. This "bonus" could/would be based on the fact that the first thread has got the lock already once and that the waiting time in the condition associated with the same lock is credited to that thread.

    I define this as "spurious" because

    • the condition has been signalled only once,
    • only one thread has been awoken by the condition
    • but the thread awoken by the condition found it was not true
    • the other thread was never touching the condition and is therefore "lucky but innocent"
    • a slightly other implementation would have prevented this.

    The last point is demonstrated with this code using Object.wait():

    public class SpuriousWakeupObject {
        static Object lock = new Object();
        static int itemsReady;
    
        public static void main(String[] args) throws Exception {
    
            // let consumer 1 enter condition wait
            new ConsumerOne().start();
            Thread.sleep(500);
    
            // let consumer 2 hit the lock
            synchronized (lock) {
                new ConsumerTwo().start();
                Thread.sleep(500);
    
                // make condition true and signal one (!) consumer
                System.out.println("Producer: fill queue");
                itemsReady = 1;
                lock.notify();
    
                Thread.sleep(500);
            } // release lock
            System.out.println("Producer: released lock");
            Thread.sleep(500);
        }
    
        abstract static class AbstractConsumer extends Thread {
            @Override
            public void run() {
                try {
                    synchronized(lock){
                        consume();
                    }
                } catch(Exception e){
                    e.printStackTrace();
                }
            }
            abstract void consume() throws Exception;
        }
    
        static class ConsumerOne extends AbstractConsumer {
            @Override
            public void consume() throws InterruptedException {
                if( itemsReady <= 0 ){      // usually this is "while"
                    System.out.println("One: Waiting...");
                    lock.wait();
                    if( itemsReady <= 0 )
                        System.out.println("One: Spurious Wakeup! Condition NOT true!");
                    else {
                        System.out.println("One: Wakeup! Let's work!");
                        --itemsReady;
                    }
                }
            }
        }
    
        static class ConsumerTwo extends AbstractConsumer {
            @Override
            public void consume() {
                if( itemsReady <= 0 )
                    System.out.println("Two: Got lock, but no work!");
                else {
                    System.out.println("Two: Got lock and immediatly start working!");
                    --itemsReady;
                }
            }
        }
    }
    

    Output:

    One: Waiting...
    Producer: fill queue
    Producer: released lock
    One: Wakeup! Let's work!
    Two: Got lock, but no work!
    

    Here the implementation seems to do as I would expect it: The thread using the condition is awoken first.

    Final note: The idea for the principle comes from Why does java.util.concurrent.ArrayBlockingQueue use 'while' loops instead of 'if' around calls to await()? , although my interpretation is different and the code is from myself.

    0 讨论(0)
提交回复
热议问题