Původní otázka, kterou jste uvedl (pokud jde o článek na wikipedii), říká, že v linuxové implementaci pthread dochází k falešným probuzením, jako vedlejší efekt signalizovaného procesu . Z vaší otázky se mi zdá, že jste vynechali "signál" (což je linuxová meziprocesová komunikační metoda) s Object.notify() (což je interní inter-thread komunikační metoda Java).
Pokud chcete pozorovat falešné probuzení - musíte spustit svůj program Java a pokusit se mu poslat nějaký signál.
Vyzkoušel jsem jednoduchý test na Linuxu, zasláním jednoduchých signálů procesu Java (jako QUIT, STOP, CONT atd.). Nezdálo se, že by to způsobilo falešné probuzení.
Takže (alespoň mně) stále není jasné, za jakých podmínek způsobí signál Linuxu falešné probuzení v Javě.
„Nepravé probuzení“ je hotová záležitost a týká se jakéhokoli detail implementace v této oblasti. Proto je docela těžké rozeznat, co je to „skutečné“ falešné probuzení a proč je jiné „neskutečné“ – natož na jaké vrstvě tento implementační detail pochází. Vyberte si libovolnou z "kernel", "system library (libc)", "JVM", "Java standart library (rt.jar)" nebo custom framework postavený na tomto stacku.
Následující program ukazuje falešné probuzení pomocí java.util.concurrent
věci:
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;
}
}
}
}
Výstup:
One: Waiting...
Producer: fill queue
Producer: released lock
Two: Got lock and immediatly start working!
One: Spurious Wakeup! Condition NOT true!
Použitý JDK byl:
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)
Je založen na jednom detailu implementace v java.util.concurrent
:Standardní Lock
má jednu čekající frontu, Condition
má další frontu. Pokud je podmínka signalizována, je signalizované vlákno přesunuto z fronty podmínek do fronty zámku. Podrobnosti implementace:Přesouvá se na konec fronty . Pokud ve frontě zámku již čeká jiné vlákno a toto druhé vlákno nenavštívilo proměnnou podmínky, může toto vlákno „ukrást“ signál. Pokud by implementace umístila první vlákno před druhé vlákno, to by se nestalo. Tento „bonus“ by mohl/by mohl být založen na skutečnosti, že první vlákno již jednou má zámek a že čekací doba ve stavu přidruženém ke stejnému zámku je připsáno tomuto vláknu.
Definuji to jako „podvržené“, protože
- podmínka byla signalizována pouze jednou,
- podmínkou bylo probuzeno pouze jedno vlákno
- ale vlákno probuzené zjištěnou podmínkou, že to není pravda
- druhé vlákno se nikdy nedotýkalo podmínky, a proto je „šťastné, ale nevinné“
- trochu jiná implementace by tomu zabránila.
Poslední bod je demonstrován tímto kódem pomocí 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;
}
}
}
}
Výstup:
One: Waiting...
Producer: fill queue
Producer: released lock
One: Wakeup! Let's work!
Two: Got lock, but no work!
Zde se zdá, že implementace funguje tak, jak bych od ní očekával:Vlákno používající podmínku je probuzeno jako první.
Poznámka na závěr: nápad protože princip pochází z Proč java.util.concurrent.ArrayBlockingQueue používá smyčky 'while' namísto 'if' kolem volání wait()? , ačkoli můj výklad je odlišný a kód pochází ode mne.
Nemůžete vynutit falešné probuzení, ale pro běžící vlákno je falešné probuzení k nerozeznání od běžného probuzení (zdroj události je jiný, ale událost samotná je stejná)
Chcete-li simulovat falešné probuzení, jednoduše zavolejte notify();
Volání interrupt()
není vhodné, protože tak nastavíte příznak přerušení a po falešném probuzení příznak přerušení není set