La question originale que vous avez mentionnée (pour autant que l'article de wikipedia) indique que de faux réveils se produisent dans l'implémentation Linux de pthread, comme effet secondaire du processus signalé . D'après votre question, il me semble que vous avez manqué le "signal" (qui est la méthode de communication inter-processus Linux) avec Object.notify() (qui est la méthode de communication inter-thread interne Java).
Si vous souhaitez observer un faux réveil, vous devez exécuter votre programme Java et essayer de lui envoyer un signal.
J'ai essayé un test simple sur Linux, en envoyant à un simple processus Java des signaux (tels que QUIT, STOP, CONT, etc.). Ceux-ci ne semblaient pas provoquer de faux réveils.
Donc (du moins pour moi), on ne sait toujours pas dans quelles conditions un signal Linux provoquera un faux réveil en Java.
"Spurious wakeup" est un pot-pourri et couvre tout détail de la mise en œuvre dans ce domaine. Par conséquent, il est assez difficile de déterminer ce qu'est un "vrai" faux réveil et pourquoi un autre est "irréel" - sans parler de la couche sur laquelle ce détail d'implémentation provient. Choisissez l'un parmi "kernel", "system library (libc)", "JVM", "Java standart library (rt.jar)" ou un framework personnalisé construit au-dessus de cette pile.
Le programme suivant montre un faux réveil utilisant java.util.concurrent
truc :
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;
}
}
}
}
Sortie :
One: Waiting...
Producer: fill queue
Producer: released lock
Two: Got lock and immediatly start working!
One: Spurious Wakeup! Condition NOT true!
Le JDK utilisé était :
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)
Il est basé sur un détail d'implémentation dans java.util.concurrent
:Le Lock
standard a une file d'attente, la Condition
a une autre file d'attente. Si la condition est signalée, le thread signalé est déplacé de la file d'attente de la condition vers la file d'attente du verrou. Le détail de l'implémentation :Il est déplacé en fin de file . Si un autre thread attend déjà dans la file d'attente de verrouillage et que ce deuxième thread n'a pas visité la variable de condition, ce thread peut "voler" le signal. Si l'implémentation aurait mis le premier thread avant le deuxième fil, cela ne serait pas arrivé. Ce "bonus" pourrait/serait basé sur le fait que le premier thread a déjà obtenu le verrou une fois et que le temps d'attente dans la condition associé au même verrou est crédité à ce fil.
Je définis cela comme "faux" parce que
- la condition n'a été signalée qu'une seule fois,
- un seul thread a été réveillé par la condition
- mais le fil réveillé par la condition a trouvé que ce n'était pas vrai
- l'autre fil n'a jamais touché la condition et est donc "chanceux mais innocent"
- une mise en œuvre légèrement différente aurait empêché cela.
Le dernier point est démontré avec ce code en utilisant 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;
}
}
}
}
Sortie :
One: Waiting...
Producer: fill queue
Producer: released lock
One: Wakeup! Let's work!
Two: Got lock, but no work!
Ici, l'implémentation semble faire ce à quoi je m'attendais :le thread utilisant la condition est réveillé en premier.
Remarque finale : L'idée car le principe vient de Pourquoi java.util.concurrent.ArrayBlockingQueue utilise-t-il des boucles 'while' au lieu de 'if' autour des appels à await()? , bien que mon interprétation soit différente et que le code soit de moi-même.
Vous ne pouvez pas forcer un faux réveil, mais pour le thread en cours d'exécution, un faux réveil est indiscernable d'un réveil normal (la source de l'événement est différente, mais l'événement lui-même est le même)
Pour simuler un faux réveil, appelez simplement notify();
Appel interrupt()
n'est pas approprié, car cela définit l'indicateur d'interruption, et après un faux réveil, l'indicateur d'interruption n'est pas définir