GNU/Linux >> LINUX-Kenntnisse >  >> Linux

Erzwingen eines falschen Aufwachens in Java

Die ursprüngliche Frage, auf die Sie sich bezogen haben (bisher als Wikipedia-Artikel), besagt, dass falsche Wakeups in der Linux-Implementierung von pthread auftreten, als Nebeneffekt des Signalisierens von Prozessen . Aus Ihrer Frage scheint mir, dass Sie "Signal" (das ist eine Linux-Interprozess-Kommunikationsmethode) mit Object.notify() (das ist eine Java-interne Inter-Thread-Kommunikationsmethode) verpasst hat.

Wenn Sie ein falsches Aufwachen beobachten möchten, müssen Sie Ihr Java-Programm ausführen und versuchen, ihm ein Signal zu senden.


Ich habe einen einfachen Test unter Linux versucht, indem ich einfache Java-Prozesssignale (wie QUIT, STOP, CONT usw.) gesendet habe. Diese schienen kein falsches Aufwachen zu verursachen.

Daher ist (zumindest für mich) immer noch nicht klar, unter welchen Bedingungen ein Linux-Signal ein falsches Aufwachen in Java verursacht.


"Spurious Wakeup" ist ein Sammelsurium und deckt jeden ab Implementierungsdetail in diesem Bereich. Daher ist es ziemlich schwer zu erkennen, was ein "echtes" falsches Aufwachen ist und warum ein anderes "unwirklich" ist - geschweige denn, auf welcher Ebene dieses Implementierungsdetail seinen Ursprung hat. Wählen Sie aus „Kernel“, „Systembibliothek (libc)“, „JVM“, „Java-Standardbibliothek (rt.jar)“ oder einem benutzerdefinierten Framework, das auf diesem Stapel aufbaut.

Das folgende Programm zeigt ein falsches Wakeup mit java.util.concurrent Sachen:

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

Ausgabe :

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

Das verwendete JDK war:

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)

Es basiert auf einem Implementierungsdetail in java.util.concurrent :Der Standard Lock hat eine Warteschlange, die Condition hat eine weitere Warteschlange. Wenn die Bedingung signalisiert wird, wird der signalisierte Thread aus der Warteschlange der Bedingung in die Warteschlange der Sperre verschoben. Das Implementierungsdetail:Es wird am Ende der Warteschlange verschoben . Wenn bereits ein anderer Thread in der Sperrwarteschlange wartet und dieser zweite Thread die Bedingungsvariable nicht besucht hat, kann dieser Thread das Signal "stehlen". Hätte die Implementierung den ersten Thread vor gesetzt Beim zweiten Thread wäre dies nicht passiert. Dieser "Bonus" könnte/würde darauf beruhen, dass der erste Thread die Sperre schon einmal bekommen hat und die Wartezeit in der Bedingung mit der gleichen Sperre verbunden ist wird diesem Thread gutgeschrieben.

Ich definiere dies als "falsch", weil

  • der Zustand wurde nur einmal signalisiert,
  • nur ein Thread wurde durch die Bedingung aufgeweckt
  • aber der Thread, der durch die Bedingung geweckt wurde, stellte fest, dass es nicht wahr war
  • der andere Thread hat die Bedingung nie berührt und ist daher "glücklich, aber unschuldig"
  • eine etwas andere Implementierung hätte dies verhindert.

Der letzte Punkt wird mit diesem Code mit Object.wait() demonstriert :

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

Ausgabe:

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

Hier scheint die Implementierung so zu sein, wie ich es erwarten würde:Der Thread, der die Bedingung verwendet, wird zuerst aufgeweckt.

Letzter Hinweis: Die Idee denn das Prinzip kommt von Warum verwendet java.util.concurrent.ArrayBlockingQueue 'while'-Schleifen anstelle von 'if' um Aufrufe von await()? , obwohl meine Interpretation anders ist und der Code von mir stammt.


Sie können ein falsches Aufwachen nicht erzwingen, aber für den laufenden Thread ist ein falsches Aufwachen nicht von einem normalen Aufwachen zu unterscheiden (die Quelle des Ereignisses ist anders, aber das Ereignis selbst ist dasselbe)

Um ein falsches Wakeup zu simulieren, rufen Sie einfach notification();

auf

Anruf interrupt() ist nicht geeignet, weil dadurch das Interrupt-Flag gesetzt wird, und nach einem falschen Aufwachen ist das Interrupt-Flag nicht einstellen


Linux
  1. So installieren Sie Java unter Ubuntu 22.04

  2. Java-Spracherkennung

  3. Rufen Sie die Unterprozess-ID in Java ab

  4. Alternativen --config Java-Bash-Skript

  5. Systemweiter Mutex in Python unter Linux

So überprüfen Sie die Java-Version

So installieren Sie Java unter Ubuntu 18.04

Installieren Sie die neueste Version von Java auf Ubuntu 18.04

So installieren Sie Java unter CentOS 8

Wie installiere ich Java unter Ubuntu 18.04?

Führen Sie ein Java-Programm im Backend aus