Подтвердить что ты не робот

Принуждение ложного пробуждения в Java

Этот вопрос не о том, действительно ли побочные пробуждения действительно счастливы, потому что это уже обсуждалось здесь на полную длину: На самом деле происходит ложное пробуждение? Поэтому это также не о том, почему мне нужно поставить цикл вокруг моего wait Statement. Что это значит:

Я хотел бы построить случай, где происходит ложное пробуждение. То, что я узнал до сих пор в связанном выше вопросе, следующее:

Если процесс Linux сигнализируется, что его ожидающие потоки будут приятное, горячее поддельное пробуждение.

Итак, похоже, что это будет работать только с Linux-машиной, на самом деле у меня Ubuntu 11.04 - 64-бит. Я написал программу Java с одним потоком, ожидающим условия, но без цикла и другого класса, на котором поток просто ждет и получает уведомление другим потоком. Я думал, что запуск всех трех потоков в одной JVM заставит описанный выше случай, но похоже, что это не так.

Есть ли еще одна идея, как построить такой случай в Java?

4b9b3361

Ответ 1

Вы не можете принудительно вызвать ложную пробуждение, но в текущем потоке ложное пробуждение неотличимо от обычного пробуждения (источник события отличается, но само событие одинаково)

Чтобы имитировать ложное пробуждение, просто вызовите notify();

Вызов interrupt() не подходит, потому что при этом устанавливается флаг прерывания, а после ложного пробуждения флаг прерывания не установлен

Ответ 2

"Поддельное пробуждение" - это hotchpotch и охватывает любую деталь реализации в этой области. Поэтому довольно сложно понять, что такое "реальное" ложное пробуждение и почему другое "нереально", не говоря уже о том, на каком слое возникает эта деталь реализации. Выберите любой из "ядро", "системная библиотека (libc)", "JVM", "стандартная библиотека Java (rt.jar)" или пользовательская структура, построенная поверх этого стека.

Следующая программа показывает ложное пробуждение, используя 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 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;
            }
        }
    }
}

Выход:

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

Используемый JDK был:

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)

Он основан на одной детали реализации в java.util.concurrent: Стандартная Lock имеет одну очередь ожидания, а Condition - очередная очередь ожидания. Если условие сигнализируется, сигнальный поток перемещается из очереди условий в очередь блокировки. Деталь реализации: он перемещается в конце очереди. Если еще один поток уже ждет в очереди блокировки, и этот второй поток не посетил переменную условия, этот поток может "украсть" сигнал. Если бы реализация поставила первый поток перед вторым потоком, этого не произошло бы. Этот "бонус" мог/был основан на том факте, что первый поток получил блокировку уже один раз и что время ожидания в условии, связанное с одной и той же блокировкой, заносится в этот поток.

Я определяю это как "ложное", потому что

  • условие сигнализировалось только один раз,
  • только один поток был пробужден условием
  • но поток, пробужденный условием, нашел, что это не правда.
  • другой поток никогда не касался состояния и, следовательно, "счастлив, но невиновен"
  • немного другая реализация предотвратила бы это.

Последняя точка демонстрируется с помощью этого кода с помощью 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 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;
            }
        }
    }
}

Вывод:

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

Здесь реализация, похоже, работает так, как я ожидал бы этого: сначала пробуждается поток с использованием условия.

Заключительное примечание: Идея принципа исходит из Почему java.util.concurrent.ArrayBlockingQueue использует "while" вместо "if" вокруг вызывает wait()?, хотя моя интерпретация различна, а код - от меня.

Ответ 3

Исходный вопрос, на который вы ссылались (насколько это известно в статье в википедии), говорит о том, что ложные пробуждения происходят в linux-реализации pthread, как побочный эффект процесса, который сигнализируется. Из вашего вопроса мне кажется, что вы пропустили "сигнал" (который представляет собой метод связи между процессами linux) с Object.notify() (который является методом внутренней межпоточной связи Java).

Если вы хотите наблюдать ложное пробуждение - вы должны запустить свою программу java и попытаться отправить ей некоторый сигнал.

Ответ 4

Я попробовал простой тест на Linux, отправив простые сигналы процесса Java (например, QUIT, STOP, CONT и т.д.). Они, похоже, не вызывали побочного пробуждения.

Итак (по крайней мере для меня) все еще неясно, при каких условиях сигнал Linux вызовет ложное пробуждение в Java.

Ответ 5

Я нашел репродуцирующего устройства, который вызывает ложные пробуждения в Java ошибка 6454029. Он запускает 30, 60, а затем 100 пар официантов/уведомлений и заставляет их ждать и уведомлять определенное количество раз. Он использует стандартные объекты Object.wait() и Object.notify(), а не объекты более высокого уровня Lock и Condition. Мне удалось использовать его, чтобы вызвать ложные пробуждения на моей 64-разрядной машине Linux с значением аргумента 1000000 с java 1.8.0-b132 и 1.6.0_45. Обратите внимание, что исходный файловый сервер жаловался на Windows XP, поэтому, по-видимому, это работает, по крайней мере, с одним ароматом окон.

Ответ 6

AFAIK, Sun JVM использует "зеленые потоки", также известные как потоки пользовательского уровня. Это означает, что потоки JVM и потоки ядра не обязательно должны сопоставлять 1-к-1. Поэтому, если спецификация не говорит об этом, я не понимаю, почему JVM будет соответствовать поведению POSIX.

Итак, несмотря на то, что спецификация указывает на возможность ложных пробуждений, необходимо создать детерминированный тест, который вызывает его. Учитывая, что потоки ядра, запущенные внутри JVM, пробуждаются по сигналу, сколько зеленых потоков вы будете просыпаться? Один? 10? Никто? Кто знает.