wait and notify () Methoden in Java

Warten Sie und notify () Methoden in Java

1. Einführung

In diesem Artikel befassen wir uns mit einem der grundlegendsten Mechanismen in Java - der Thread-Synchronisation.

Wir werden zunächst einige wesentliche Begriffe und Methoden im Zusammenhang mit der Parallelität erörtern.

Und wir werden eine einfache Anwendung entwickeln, in der wir uns mit Parallelitätsproblemen befassen, umwait() undnotify(). besser zu verstehen

2. Thread-Synchronisation in Java

In einer Multithread-Umgebung versuchen möglicherweise mehrere Threads, dieselbe Ressource zu ändern. Wenn Threads nicht ordnungsgemäß verwaltet werden, führt dies natürlich zu Konsistenzproblemen.

2.1. Geschützte Blöcke in Java

Ein Werkzeug, mit dem wir Aktionen mehrerer Threads in Java koordinieren können, sind geschützte Blöcke. Solche Blöcke überprüfen eine bestimmte Bedingung, bevor die Ausführung fortgesetzt wird.

In diesem Sinne werden wir Folgendes verwenden:

Dies lässt sich anhand des folgenden Diagramms besser verstehen, das den Lebenszyklus vonThread darstellt:

image

Bitte beachten Sie, dass es viele Möglichkeiten gibt, diesen Lebenszyklus zu steuern. In diesem Artikel konzentrieren wir uns jedoch nur aufwait() undnotify().

3. Diewait()-Methode

Einfach ausgedrückt, wenn wirwait() – aufrufen, wird der aktuelle Thread gezwungen zu warten, bis ein anderer Threadnotify() odernotifyAll() für dasselbe Objekt aufruft.

Dazu muss der aktuelle Thread den Monitor des Objekts besitzen. LautJavadocs kann dies passieren, wenn:

  • Wir haben die Instanzmethode vonsynchronizedfür das angegebene Objekt ausgeführt

  • Wir haben den Hauptteil einessynchronized-Blocks für das angegebene Objekt ausgeführt

  • durch Ausführen vonsynchronized static-Methoden für Objekte vom TypClass

Beachten Sie, dass jeweils nur ein aktiver Thread den Monitor eines Objekts besitzen kann.

Diesewait()-Methode enthält drei überladene Signaturen. Schauen wir uns diese an.

3.1. wait()

Die Methodewait() bewirkt, dass der aktuelle Thread unbegrenzt wartet, bis ein anderer Thread entwedernotify() für dieses Objekt odernotifyAll() aufruft.

3.2. wait(long timeout)

Mit dieser Methode können wir ein Timeout festlegen, nach dem der Thread automatisch geweckt wird. Ein Thread kann vor Erreichen des Timeouts mitnotify() odernotifyAll(). aufgeweckt werden

Beachten Sie, dass der Aufruf vonwait(0) mit dem Aufruf vonwait(). identisch ist

3.3. wait(long timeout, int nanos)

Dies ist eine weitere Signatur, die dieselbe Funktionalität bietet, mit dem einzigen Unterschied, dass wir eine höhere Präzision liefern können.

Die gesamte Zeitüberschreitungsperiode (in Nanosekunden) wird als1_000_000*timeout + nanos. berechnet

4. notify() undnotifyAll()

Die Methodenotify()wird zum Aufwecken von Threads verwendet, die auf den Zugriff auf den Monitor dieses Objekts warten.

Es gibt zwei Möglichkeiten, wartende Threads zu benachrichtigen.

4.1. notify()

Für alle Threads, die auf dem Monitor dieses Objekts warten (unter Verwendung einer derwait()-Methoden), benachrichtigt die Methodenotify() einen von ihnen, willkürlich aufzuwachen. Die Wahl, welcher Thread genau geweckt werden soll, ist nicht deterministisch und hängt von der Implementierung ab.

Danotify() einen einzelnen zufälligen Thread aufweckt, kann es verwendet werden, um sich gegenseitig ausschließende Sperren zu implementieren, wenn Threads ähnliche Aufgaben ausführen. In den meisten Fällen wäre es jedoch praktikabler,notifyAll() zu implementieren.

4.2. notifyAll()

Diese Methode weckt einfach alle Threads, die auf dem Monitor dieses Objekts warten.

Die aufgeweckten Fäden werden auf die übliche Weise vervollständigt - wie bei jedem anderen Faden.

Bevor wir jedoch zulassen, dass ihre Ausführung fortgesetzt wird, immerdefine a quick check for the condition required to proceed with the thread - da es einige Situationen geben kann, in denen der Thread ohne Benachrichtigung aufgeweckt wurde (dieses Szenario wird später in einem Beispiel erläutert).

5. Problem bei der Sender-Empfänger-Synchronisation

Nachdem wir die Grundlagen verstanden haben, gehen wir eine einfache Anwendung vonSender -Receiver durch, die die Methodenwait() undnotify()verwendet, um die Synchronisation zwischen ihnen einzurichten:

  • DieSender sollen ein Datenpaket an dieReceiver senden

  • DieReceiver können das Datenpaket erst verarbeiten, wenn dieSender das Senden beendet haben

  • Ebenso dürfen dieSender nicht versuchen, ein anderes Paket zu senden, es sei denn, dieReceiver haben das vorherige Paket bereits verarbeitet

Erstellen wir zunächst die KlasseData, die aus den Datenpacket besteht, die vonSender anReceiver. gesendet werden. Wir verwendenwait() undnotifyAll() So richten Sie die Synchronisation zwischen ihnen ein:

public class Data {
    private String packet;

    // True if receiver should wait
    // False if sender should wait
    private boolean transfer = true;

    public synchronized void send(String packet) {
        while (!transfer) {
            try {
                wait();
            } catch (InterruptedException e)  {
                Thread.currentThread().interrupt();
                Log.error("Thread interrupted", e);
            }
        }
        transfer = false;

        this.packet = packet;
        notifyAll();
    }

    public synchronized String receive() {
        while (transfer) {
            try {
                wait();
            } catch (InterruptedException e)  {
                Thread.currentThread().interrupt();
                Log.error("Thread interrupted", e);
            }
        }
        transfer = true;

        notifyAll();
        return packet;
    }
}

Lassen Sie uns zusammenfassen, was hier vor sich geht:

  • Die Variablepacket bezeichnet die Daten, die über das Netzwerk übertragen werden

  • Wir haben eineboolean-Variabletransfer –, die dieSender undReceiver für die Synchronisation verwenden:

    • Wenn diese Variabletrue ist, solltenReceiver darauf warten, dassSender die Nachricht sendet

    • Wenn esfalse ist, sollteSender warten, bisReceiver die Nachricht empfangen

  • Sender verwendet die Methodesend(), um Daten anReceiver zu senden:

    • Wenntransferfalse, ist, warten wir, indem wirwait() in diesem Thread aufrufen

    • Wenn es jedochtrue ist, schalten wir den Status um, setzen unsere Nachricht und rufennotifyAll() auf, um andere Threads zu aktivieren, um anzugeben, dass ein signifikantes Ereignis aufgetreten ist, und sie können prüfen, ob sie die Ausführung fortsetzen können

  • In ähnlicher Weise verwenden dieReceiver die Methodereceive():

    • Wenntransfer durchSender auffalse gesetzt wurde, wird nur fortgefahren, andernfalls rufen wirwait() in diesem Thread auf

    • Wenn die Bedingung erfüllt ist, schalten wir den Status um, benachrichtigen alle wartenden Threads, um aufzuwachen, und geben das Datenpaket zurück, dasReceiver war

5.1. Warumwait() in einewhile-Schleife einschließen?

Danotify() undnotifyAll() zufällig Threads aufwecken, die auf dem Monitor dieses Objekts warten, ist es nicht immer wichtig, dass die Bedingung erfüllt ist. Manchmal kann es vorkommen, dass der Thread aufgeweckt wird, die Bedingung jedoch noch nicht erfüllt ist.

Wir können auch eine Prüfung definieren, um uns vor ungewollten Aufweckvorgängen zu schützen. Dabei kann ein Thread vom Warten aufwachen, ohne jemals eine Benachrichtigung erhalten zu haben.

5.2. Warum müssen wir die Methoden send() undreceive() synchronisieren?

Wir haben diese Methoden insynchronized Methoden eingefügt, um intrinsische Sperren bereitzustellen. Wenn ein Thread, der die Methodewait()aufruft, die inhärente Sperre nicht besitzt, wird ein Fehler ausgelöst.

Wir werden jetztSender undReceiver erstellen und dieRunnable-Schnittstelle auf beiden implementieren, damit ihre Instanzen von einem Thread ausgeführt werden können.

Lassen Sie uns zuerst sehen, wieSender funktionieren:

public class Sender implements Runnable {
    private Data data;

    // standard constructors

    public void run() {
        String packets[] = {
          "First packet",
          "Second packet",
          "Third packet",
          "Fourth packet",
          "End"
        };

        for (String packet : packets) {
            data.send(packet);

            // Thread.sleep() to mimic heavy server-side processing
            try {
                Thread.sleep(ThreadLocalRandom.current().nextInt(1000, 5000));
            } catch (InterruptedException e)  {
                Thread.currentThread().interrupt();
                Log.error("Thread interrupted", e);
            }
        }
    }
}

Für dieseSender:

  • Wir erstellen einige zufällige Datenpakete, die inpackets[] Array über das Netzwerk gesendet werden

  • Für jedes Paket rufen wir lediglichsend() auf

  • Dann rufen wirThread.sleep() mit einem zufälligen Intervall auf, um eine starke serverseitige Verarbeitung nachzuahmen

Zum Schluss implementieren wir unsereReceiver:

public class Receiver implements Runnable {
    private Data load;

    // standard constructors

    public void run() {
        for(String receivedMessage = load.receive();
          !"End".equals(receivedMessage);
          receivedMessage = load.receive()) {

            System.out.println(receivedMessage);

            // ...
            try {
                Thread.sleep(ThreadLocalRandom.current().nextInt(1000, 5000));
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                Log.error("Thread interrupted", e);
            }
        }
    }
}

Hier rufen wir einfachload.receive() in der Schleife auf, bis wir das letzte“End”-Datenpaket erhalten.

Lassen Sie uns diese Anwendung jetzt in Aktion sehen:

public static void main(String[] args) {
    Data data = new Data();
    Thread sender = new Thread(new Sender(data));
    Thread receiver = new Thread(new Receiver(data));

    sender.start();
    receiver.start();
}

Wir erhalten folgende Ausgabe:

First packet
Second packet
Third packet
Fourth packet

Und hier sind wir -we’ve received all data packets in the right, sequential order und haben erfolgreich die richtige Kommunikation zwischen Sender und Empfänger hergestellt.

6. Fazit

In diesem Artikel haben wir einige zentrale Synchronisationskonzepte in Java erörtert. Genauer gesagt haben wir uns darauf konzentriert, wie wirwait() undnotify() verwenden können, um interessante Synchronisationsprobleme zu lösen. Schließlich haben wir ein Codebeispiel durchgesehen, in dem wir diese Konzepte in der Praxis angewendet haben.

Bevor wir hier abschließen, ist es erwähnenswert, dass all diese Low-Level-APIs wiewait(),notify() undnotifyAll() traditionelle Methoden sind, die gut funktionieren, aber übergeordnete Mechanismen oft einfacher und besser - wie die nativen Java-SchnittstellenLock undCondition von Java (verfügbar im Paketjava.util.concurrent.locks).

Weitere Informationen zum Paketjava.util.concurrentfinden Sie in unserem Artikeloverview of the java.util.concurrent.LockundConditionind inguide to java.util.concurrent.Locks, hereenthalten.

Wie immer sind die in diesem Artikel verwendeten vollständigen Codefragmenteover on GitHub. verfügbar