GNU/Linux >> Tutoriels Linux >  >> Linux

Forcer un faux réveil en Java

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


Linux
  1. Spinlock contre Sémaphore

  2. Reconnaissance vocale Java

  3. Obtenir l'identifiant du sous-processus en Java

  4. alternatives --config script bash java

  5. Mutex à l'échelle du système en Python sur Linux

Comment vérifier la version Java

Comment installer Java sur Ubuntu 18.04

Installez la dernière version de Java sur Ubuntu 18.04

Comment installer Java sur CentOS 8

Comment installer Java sur Ubuntu 18.04 ?

Exécuter un programme java en backend