Этот вопрос не о том, действительно ли ложные пробуждения счастливы, потому что это уже подробно обсуждалось здесь: Действительно ли ложные пробуждения случаются? Поэтому это также не о том, почему я должен использовать цикл вокруг моего оператора wait
. О чем это:
Я хотел бы построить случай , где происходит ложное пробуждение. Что я узнал до сих пор из вопроса, связанного выше, это следующее:
Если процесс Linux получает сигнал, его ожидающие потоки будут получать приятное горячее ложное пробуждение.
Похоже, это будет работать только на Linux-машине, на самом деле у меня Ubuntu 11.04 - 64-бит. Я написал программу на Java с одним потоком, ожидающим условия, но без цикла, и с другим классом, в котором поток просто ждет и получает уведомление от другого потока. Я думал, что запуск всех трех потоков в одной JVM вызовет описанный выше случай, но похоже, что это не так.
Есть ли у кого-нибудь еще идея, как построить такой случай в Java?
Нельзя принудительно вызвать ложное пробуждение, но для работающего потока ложное пробуждение неотличимо от обычного пробуждения (источник события отличается, но само событие то же самое)
Для имитации ложного пробуждения просто вызовите notify () ;
Вызов interrupt()
не подходит, поскольку при этом устанавливается флаг прерывания, а после ложного пробуждения - флаг прерывания. не установлено
Исходный вопрос, на который вы ссылались (поскольку это статья в Википедии), говорит, что ложные пробуждения происходят в реализации pthread в linux, как побочный эффект сигнализируемого процесса . Из вашего вопроса мне кажется, что вы пропустили «сигнал» (который является методом межпроцессного взаимодействия linux) с Object.notify () (который является внутренним методом связи между потоками Java).
Если вы хотите наблюдать ложное пробуждение - вы должны запустить вашу Java-программу и попытаться отправить ей какой-нибудь сигнал.
«Ложное пробуждение» - это «горячая точка», которая охватывает любые детали реализации в этой области. Поэтому довольно трудно понять, что такое «реальное» ложное пробуждение и почему другое «нереально», не говоря уже о том, на каком уровне происходит эта деталь реализации. Выберите любой из «ядра», «системная библиотека (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 ()? , хотя моя интерпретация отличается, и код от меня.
Я попытался провести простой тест в Linux, отправив простые сигналы процесса Java (такие как QUIT, STOP, CONT и т. Д.). Похоже, это не вызвало ложного пробуждения.
Так (по крайней мере для меня) все еще не ясно, при каких условиях сигнал Linux вызовет ложное пробуждение в Java.
Я нашел репродуктор, который вызывает ложные пробуждения в 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, так что, по-видимому, это работает как минимум на одном варианте окон.
AFAIK, Sun JVM использует «зеленые потоки», также известные как потоки пользовательского уровня. Это означает, что потоки JVM и потоки ядра на самом деле не должны отображать 1-к-1. Поэтому, если в спецификации это не указано, я не понимаю, почему JVM будет соответствовать поведению POSIX.
Таким образом, даже если в спецификации упоминается возможность ложных пробуждений, необходимо создать детерминистический тест, который его вызывает. Учитывая, что потоки ядра, работающие внутри JVM, просыпаются по сигналу, сколько зеленых потоков вы пробудитесь? Один? Десять? Никто? Кто знает.
Количество ложных пробуждений прямо пропорционально количеству ядер процессора в вашей системе.
В системе с 24 ядрами должно быть легко встретить ложные пробуждения. (Некоторые блоггеры зашли бы так далеко, чтобы заявить, что примерно 40% их ожиданий прерывается в этих системах. Но, безусловно, есть и другие факторы, которые следует учитывать в уравнении, что усложняет определение числа.)