Принудительное пробуждение в Java - PullRequest
28 голосов
/ 15 июля 2011

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

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

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

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

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

Ответы [ 7 ]

17 голосов
/ 15 июля 2011

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

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

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

9 голосов
/ 18 октября 2011

«Ложное пробуждение» - это горячая точка, охватывающая любую деталь реализации в этой области.Поэтому довольно трудно понять, что такое «реальное» ложное пробуждение и почему другое «нереально», не говоря уже о том, на каком уровне происходит эта деталь реализации.Выберите любой из «ядра», «системная библиотека (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'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!

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

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

3 голосов
/ 19 июля 2011

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

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

2 голосов
/ 20 июля 2011

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

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

1 голос
/ 25 октября 2014

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

0 голосов
/ 11 февраля 2019

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

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

0 голосов
/ 24 июля 2011

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

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

...