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