Принудительное ложное пробуждение в Java

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

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

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

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

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

28
задан Community 23 May 2017 в 12:17
поделиться

7 ответов

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

Для имитации ложного пробуждения просто вызовите notify () ;

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

17
ответ дан 28 November 2019 в 03:43
поделиться

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

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

3
ответ дан 28 November 2019 в 03:43
поделиться

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

Следующая программа показывает ложное пробуждение с использованием java.util.concurrent вещи:

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;
            }
        }
    }
}

Вывод:

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'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;
            }
        }
    }
}

Вывод:

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

Здесь реализация, кажется, работает так, как я ожидаю : Поток, использующий условие, пробуждается первым.

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

9
ответ дан 28 November 2019 в 03:43
поделиться

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

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

2
ответ дан 28 November 2019 в 03:43
поделиться

Я нашел репродуктор, который вызывает ложные пробуждения в 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, так что, по-видимому, это работает как минимум на одном варианте окон.

1
ответ дан 28 November 2019 в 03:43
поделиться

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

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

0
ответ дан 28 November 2019 в 03:43
поделиться

Количество ложных пробуждений прямо пропорционально количеству ядер процессора в вашей системе.

В системе с 24 ядрами должно быть легко встретить ложные пробуждения. (Некоторые блоггеры зашли бы так далеко, чтобы заявить, что примерно 40% их ожиданий прерывается в этих системах. Но, безусловно, есть и другие факторы, которые следует учитывать в уравнении, что усложняет определение числа.)

0
ответ дан 28 November 2019 в 03:43
поделиться
Другие вопросы по тегам:

Похожие вопросы: