Anleitung zum Java Phaser

Anleitung zum Java Phaser

1. Überblick

In diesem Artikel betrachten wir das KonstruktPhaseraus dem Paketjava.util.concurrent. Es ist ein sehr ähnliches Konstrukt wieCountDownLatch, mit dem wir die Ausführung von Threads koordinieren können. Im Vergleich zuCountDownLatch verfügt es über einige zusätzliche Funktionen.

Phaser ist eine Barriere, auf der die dynamische Anzahl von Threads warten muss, bevor die Ausführung fortgesetzt wird. InCountDownLatch kann diese Nummer nicht dynamisch konfiguriert werden und muss beim Erstellen der Instanz angegeben werden.

2. Phaser API

MitPhaser können wir eine Logik erstellen, in derthreads need to wait on the barrier before going to the next step of execution enthalten ist.

Wir können mehrere Ausführungsphasen koordinieren und für jede Programmphase einePhaser-Instanz wiederverwenden. Jede Phase kann eine andere Anzahl von Threads haben, die darauf warten, zu einer anderen Phase überzugehen. Wir werden uns später ein Beispiel für die Verwendung von Phasen ansehen.

Um an der Koordination teilzunehmen, muss sich der Thread mit der InstanzPhaserelbstregister(). Beachten Sie, dass dies nur die Anzahl der registrierten Parteien erhöht und wir nicht überprüfen können, ob der aktuelle Thread registriert ist. Wir müssten die Implementierung in Unterklassen unterteilen, um dies zu unterstützen.

Der Thread signalisiert, dass er an der Barriere angekommen ist, indem erarriveAndAwaitAdvance() aufruft, was eine Blockierungsmethode ist. When the number of arrived parties is equal to the number of registered parties, the execution of the program will continue und die Phasenzahl erhöht sich. Wir können die aktuelle Phasennummer erhalten, indem wir die MethodegetPhase()aufrufen.

Wenn der Thread seine Arbeit beendet hat, sollten wir die MethodearriveAndDeregister()aufrufen, um zu signalisieren, dass der aktuelle Thread in dieser bestimmten Phase nicht mehr berücksichtigt werden soll.

3. Implementieren der Logik mithilfe derPhaser-API

Nehmen wir an, wir möchten mehrere Aktionsphasen koordinieren. Drei Threads verarbeiten die erste Phase und zwei Threads verarbeiten die zweite Phase.

Wir erstellen eineLongRunningAction-Klasse, die dieRunnable-Schnittstelle implementiert:

class LongRunningAction implements Runnable {
    private String threadName;
    private Phaser ph;

    LongRunningAction(String threadName, Phaser ph) {
        this.threadName = threadName;
        this.ph = ph;
        ph.register();
    }

    @Override
    public void run() {
        ph.arriveAndAwaitAdvance();
        try {
            Thread.sleep(20);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        ph.arriveAndDeregister();
    }
}

Wenn unsere Aktionsklasse instanziiert wird, registrieren wir uns mit derregister()-Methode bei der Instanz vonPhaser. Dadurch wird die Anzahl der Threads mit diesen bestimmtenPhaser. erhöht

Der Aufruf vonarriveAndAwaitAdvance() bewirkt, dass der aktuelle Thread auf der Barriere wartet. Wie bereits erwähnt, wird die Ausführung fortgesetzt, wenn die Anzahl der angekommenen Parteien der Anzahl der registrierten Parteien entspricht.

Nach Abschluss der Verarbeitung wird der aktuelle Thread durch Aufrufen der MethodearriveAndDeregister()elbst abgemeldet.

Erstellen wir einen Testfall, in dem wir dreiLongRunningAction Threads starten und die Barriere blockieren. Als nächstes erstellen wir nach Abschluss der Aktion zwei zusätzlicheLongRunningAction-Threads, die die Verarbeitung der nächsten Phase durchführen.

Beim Erstellen der Instanz vonPhaseraus dem Hauptthread übergeben wir1 als Argument. Dies entspricht dem Aufrufen derregister()-Methode aus dem aktuellen Thread. Wir tun dies, weil beim Erstellen von drei Arbeitsthreads der Hauptthread ein Koordinator ist und daher fürPhaservier Threads registriert sein müssen:

ExecutorService executorService = Executors.newCachedThreadPool();
Phaser ph = new Phaser(1);

assertEquals(0, ph.getPhase());

Die Phase nach der Initialisierung ist gleich Null.

Die KlassePhaserhat einen Konstruktor, in dem wir eine übergeordnete Instanz an sie übergeben können. Dies ist in Fällen nützlich, in denen wir eine große Anzahl von Parteien haben, die massive Kosten für Synchronisationskonflikte verursachen würden. In solchen Situationen können Instanzen vonPhasers so eingerichtet werden, dass Gruppen von Subphasern ein gemeinsames Elternteil haben.

Als nächstes starten wir dreiLongRunningAction Aktionsthreads, die auf der Barriere warten, bis wir diearriveAndAwaitAdvance() Methode vom Hauptthread aus aufrufen.

Denken Sie daran, dass wir unserePhaser mit1 initialisiert undregister() noch dreimal aufgerufen haben. Jetzt haben drei Aktionsthreads angekündigt, dass sie an der Barriere angekommen sind. Daher ist ein weiterer Aufruf vonarriveAndAwaitAdvance() erforderlich - der vom Hauptthread:

executorService.submit(new LongRunningAction("thread-1", ph));
executorService.submit(new LongRunningAction("thread-2", ph));
executorService.submit(new LongRunningAction("thread-3", ph));

ph.arriveAndAwaitAdvance();

assertEquals(1, ph.getPhase());

Nach Abschluss dieser Phase gibt die MethodegetPhase() eins zurück, da das Programm die Verarbeitung des ersten Ausführungsschritts abgeschlossen hat.

Angenommen, zwei Threads sollten die nächste Verarbeitungsphase durchführen. Wir könnenPhaser nutzen, um dies zu erreichen, da wir so die Anzahl der Threads, die auf die Barriere warten sollen, dynamisch konfigurieren können. Wir starten zwei neue Threads, aber diese werden erst ausgeführt, wennarriveAndAwaitAdvance() vom Hauptthread aufgerufen wird (wie im vorherigen Fall):

executorService.submit(new LongRunningAction("thread-4", ph));
executorService.submit(new LongRunningAction("thread-5", ph));
ph.arriveAndAwaitAdvance();

assertEquals(2, ph.getPhase());

ph.arriveAndDeregister();

Danach gibt diegetPhase()-Methode eine Phasennummer von zwei zurück. Wenn wir unser Programm beenden möchten, müssen wir die MethodearriveAndDeregister() aufrufen, da der Hauptthread immer noch inPhaser. registriert ist. Wenn die Abmeldung bewirkt, dass die Anzahl der registrierten Parteien Null wird, wirdPhaser) s istterminated. Alle Aufrufe von Synchronisationsmethoden werden nicht mehr blockiert und kehren sofort zurück.

Das Ausführen des Programms erzeugt die folgende Ausgabe (der vollständige Quellcode mit den Anweisungen für die Druckzeile befindet sich im Code-Repository):

This is phase 0
This is phase 0
This is phase 0
Thread thread-2 before long running action
Thread thread-1 before long running action
Thread thread-3 before long running action
This is phase 1
This is phase 1
Thread thread-4 before long running action
Thread thread-5 before long running action

Wir sehen, dass alle Threads auf die Ausführung warten, bis sich die Barriere öffnet. Die nächste Phase der Ausführung wird nur ausgeführt, wenn die vorherige erfolgreich abgeschlossen wurde.

4. Fazit

In diesem Tutorial haben wir uns das KonstruktPhaser ausjava.util.concurrent angesehen und die Koordinationslogik mit mehreren Phasen unter Verwendung der KlassePhaser implementiert.

Die Implementierung all dieser Beispiele und Codefragmente finden Sie inGitHub project - dies ist ein Maven-Projekt, daher sollte es einfach zu importieren und auszuführen sein, wie es ist.