GNU/Linux >> Linux Esercitazione >  >> Linux

Forzare un risveglio spurio in Java

La domanda originale a cui hai fatto riferimento (per quanto riguarda l'articolo di wikipedia) dice che si verificano risvegli spuri nell'implementazione Linux di pthread, come effetto collaterale del processo segnalato . Dalla tua domanda mi sembra che tu abbia perso il "segnale" (che è il metodo di comunicazione inter-processo di Linux) con Object.notify() (che è il metodo di comunicazione inter-thread interno java).

Se vuoi osservare un risveglio spurio, devi eseguire il tuo programma java e provare a inviargli un segnale.


Ho provato un semplice test su Linux, inviando un semplice segnale di processo Java (come QUIT, STOP, CONT, ecc.). Questi non sembravano causare un risveglio spurio.

Quindi (almeno per me) non è ancora chiaro in quali condizioni un segnale Linux causerà un risveglio spurio in Java.


"Spurious wakeup" è un miscuglio e copre qualsiasi dettagli di implementazione in quel regno. Pertanto è abbastanza difficile capire cosa sia un risveglio spurio "reale" e perché un altro sia "irreale" - figuriamoci su quale livello abbia origine questo dettaglio di implementazione. Scegline uno tra "kernel", "libreria di sistema (libc)", "JVM", "libreria standard Java (rt.jar)" o un framework personalizzato costruito sopra questo stack.

Il seguente programma mostra un wakeup spurio utilizzando java.util.concurrent roba:

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;
            }
        }
    }
}

Uscita :

One: Waiting...
Producer: fill queue
Producer: released lock
Two: Got lock and immediatly start working!
One: Spurious Wakeup! Condition NOT true!

Il JDK utilizzato era:

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)

Si basa su un dettaglio di implementazione in java.util.concurrent :Il Lock standard ha una coda di attesa, la Condition ha un'altra coda di attesa. Se la condizione viene segnalata, il thread segnalato viene spostato dalla coda della condizione alla coda del blocco. Il dettaglio dell'implementazione:viene spostato alla fine della coda . Se un altro thread è già in attesa nella coda di blocco e questo secondo thread non ha visitato la variabile di condizione, questo thread può "rubare" il segnale. Se l'implementazione avesse messo il primo thread prima il secondo thread, questo non sarebbe successo. Questo "bonus" potrebbe/sarebbe basato sul fatto che il primo thread ha già ottenuto il lock una volta e che il tempo di attesa nella condizione associata allo stesso lock è accreditato a quel thread.

Lo definisco "spurio" perché

  • la condizione è stata segnalata una sola volta,
  • solo un thread è stato risvegliato dalla condizione
  • ma il thread risvegliato dalla condizione ha rilevato che non era vero
  • l'altro thread non ha mai toccato la condizione ed è quindi "fortunato ma innocente"
  • un'implementazione leggermente diversa lo avrebbe impedito.

L'ultimo punto è dimostrato con questo codice usando 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;
            }
        }
    }
}

Uscita:

One: Waiting...
Producer: fill queue
Producer: released lock
One: Wakeup! Let's work!
Two: Got lock, but no work!

Qui l'implementazione sembra fare come mi sarei aspettato:il thread che utilizza la condizione viene risvegliato per primo.

Nota finale: L'idea perché il principio deriva da Perché java.util.concurrent.ArrayBlockingQueue usa i cicli 'while' invece di 'if' intorno alle chiamate a await()? , anche se la mia interpretazione è diversa e il codice è mio.


Non puoi forzare un risveglio spurio, ma per il thread in esecuzione, un risveglio spurio è indistinguibile da un risveglio normale (l'origine dell'evento è diversa, ma l'evento stesso è lo stesso)

Per simulare un risveglio spurio, chiama semplicemente notify();

Chiamando interrupt() non è adatto, perché così facendo viene impostato il flag di interruzione e, dopo un risveglio spurio, il flag di interruzione non imposta


Linux
  1. Correzione E:impossibile ottenere il blocco /var/cache/apt/archives/lock [Suggerimento rapido]

  2. Come uccidere il processo che tiene il blocco Apt?

  3. Ritardo blocco maiuscole?

  4. Java 10 Webupd8?

  5. Mutex a livello di sistema in Python su Linux

Installa Java manualmente su Linux

Come controllare la versione Java

Come installare Java su Ubuntu 18.04

Installa l'ultima versione di Java su Ubuntu 18.04

Come installare Java su CentOS 8

Come installare Java su Ubuntu 18.04?