OpenJDK Project Loom

1. Übersicht

In diesem Artikel werfen wir einen kurzen Blick auf Project Loom. Im Wesentlichen besteht das Hauptziel von Project Loom darin, ein leichtes Parallelitätsmodell mit hohem Durchsatz in Java zu unterstützen.

2. Projektwebstuhl

Project Loom ist ein Versuch der OpenJDK-Community, Java ein leichtes Parallelitätskonstrukt einzuführen. Die Prototypen für Loom haben bisher eine Änderung in der JVM sowie in der Java-Bibliothek eingeführt.

Obwohl es noch keine geplante Veröffentlichung für Loom gibt, können wir auf die neuesten Prototypen im Wiki von Project Loom zugreifen.

Bevor wir die verschiedenen Konzepte von Loom diskutieren, wollen wir das aktuelle Parallelitätsmodell in Java diskutieren.

3. Javas Parallelitätsmodell

Derzeit repräsentiert Thread die Kernabstraktion der Parallelität in Java. Diese Abstraktion erleichtert zusammen mit anderen gleichzeitigen APIs das Schreiben gleichzeitiger Anwendungen.

Da Java jedoch die Betriebssystem-Kernel-Threads für die Implementierung verwendet, erfüllt es nicht die heutigen Anforderungen an die Parallelität. Es gibt insbesondere zwei Hauptprobleme:

  1. Threads können nicht mit der Skalierung der Parallelitätseinheit der Domäne übereinstimmen. Beispielsweise erlauben Anwendungen normalerweise bis zu Millionen von Transaktionen, Benutzern oder Sitzungen. Die Anzahl der vom Kernel unterstützten Threads ist jedoch viel geringer. Daher ist ein T hread für jeden Benutzer, jede Transaktion oder Sitzung oft nicht möglich.
  2. Die meisten gleichzeitigen Anwendungen benötigen für jede Anforderung eine Synchronisierung zwischen den Threads. Aus diesem Grund findet ein teurer Kontextwechsel zwischen Betriebssystem-Threads statt.

Eine mögliche Lösung für solche Probleme ist die Verwendung von asynchronen gleichzeitigen APIs . Häufige Beispiele sind CompletableFuture und RxJava. Vorausgesetzt, dass solche APIs den Kernel-Thread nicht blockieren, gibt es einer Anwendung ein feinkörnigeres Parallelitätskonstrukt über Java-Threads .

Andererseits ist es schwieriger , solche APIs zu debuggen und in ältere APIs zu integrieren . Daher besteht ein Bedarf an einem kompakten Parallelitätskonstrukt, das von Kernel-Threads unabhängig ist.

4. Aufgaben und Planer

Jede Implementierung eines Threads, entweder leicht oder schwer, hängt von zwei Konstrukten ab:

  1. Aufgabe (auch als Fortsetzung bezeichnet) - Eine Folge von Anweisungen, die sich für einige Blockierungsvorgänge aussetzen können
  2. Scheduler - Zum Zuweisen der Fortsetzung zur CPU und zum Neuzuweisen der CPU aus einer angehaltenen Fortsetzung

Derzeit stützt sich Java sowohl für die Fortsetzung als auch für den Scheduler auf Betriebssystemimplementierungen .

Um eine Fortsetzung auszusetzen, muss nun der gesamte Aufrufstapel gespeichert werden. Rufen Sie in ähnlicher Weise den Aufrufstapel bei Wiederaufnahme ab. Da die Betriebssystemimplementierung von Fortsetzungen den nativen Aufrufstapel zusammen mit dem Java-Aufrufstapel enthält, führt dies zu einem hohen Platzbedarf .

Ein größeres Problem ist jedoch die Verwendung des OS-Schedulers. Da der Scheduler im Kernelmodus ausgeführt wird, gibt es keine Unterscheidung zwischen Threads. Und es behandelt jede CPU-Anfrage auf die gleiche Weise.

Diese Art der Planung ist insbesondere für Java-Anwendungen nicht optimal .

Stellen Sie sich beispielsweise einen Anwendungsthread vor, der eine Aktion für die Anforderungen ausführt und die Daten dann zur weiteren Verarbeitung an einen anderen Thread weiterleitet. Hier ist es besser, beide Threads auf derselben CPU zu planen . Da der Scheduler jedoch unabhängig von dem Thread ist, der die CPU anfordert, kann dies nicht garantiert werden.

Project Loom schlägt vor, dies durch Threads im Benutzermodus zu lösen, die auf der Java-Laufzeitimplementierung von Fortsetzungen und Schedulern anstelle der Betriebssystemimplementierung beruhen .

5. Fasern

In den jüngsten Prototypen in OpenJDK wird neben der Thread- Klasse eine neue Klasse namens Fibre in die Bibliothek eingeführt.

Da die geplante Bibliothek für Glasfasern Thread ähnelt, sollte auch die Benutzerimplementierung ähnlich bleiben. Es gibt jedoch zwei Hauptunterschiede:

  1. Fibre würde jede Aufgabe in eine interne Fortsetzung im Benutzermodus einschließen. Dies würde es der Aufgabe ermöglichen, die Java-Laufzeit anstelle des Kernels anzuhalten und fortzusetzen
  2. Ein steckbarer Benutzermodus-Scheduler ( z. B. ForkJoinPool ) würde verwendet

Lassen Sie uns diese beiden Punkte im Detail durchgehen.

6. Fortsetzung

Eine Fortsetzung (oder Co-Routine) ist eine Folge von Anweisungen, die vom Anrufer zu einem späteren Zeitpunkt ausgegeben und wieder aufgenommen werden können.

Jede Fortsetzung hat einen Einstiegspunkt und eine Fließgrenze. Die Streckgrenze ist dort, wo sie ausgesetzt wurde. Immer wenn der Anrufer die Fortsetzung fortsetzt, kehrt die Steuerung zur letzten Fließgrenze zurück.

Es ist wichtig zu wissen, dass dieses Anhalten / Fortsetzen jetzt in der Sprachlaufzeit anstelle des Betriebssystems erfolgt . Daher wird der teure Kontextwechsel zwischen Kernel-Threads verhindert.

Ähnlich wie bei Threads zielt Project Loom darauf ab, verschachtelte Fasern zu unterstützen. Da Fasern intern auf Fortsetzungen beruhen, müssen auch verschachtelte Fortsetzungen unterstützt werden. Um dies zu verstehen besser, sollten Sie eine Klasse Fortsetzung , die Verschachtelung erlaubt:

Continuation cont1 = new Continuation(() -> { Continuation cont2 = new Continuation(() -> { //do something suspend(SCOPE_CONT_2); suspend(SCOPE_CONT_1); }); });

Wie oben gezeigt, kann die verschachtelte Fortsetzung sich selbst oder eine der umschließenden Fortsetzungen durch Übergeben einer Bereichsvariablen aussetzen . Aus diesem Grund werden sie als fortlaufende Fortsetzungen bezeichnet.

Da das Anhalten einer Fortsetzung auch das Speichern des Aufrufstapels erfordern würde, ist es auch ein Ziel des Projekts Loom, einen leichten Stapelabruf hinzuzufügen, während die Fortsetzung fortgesetzt wird.

7. Scheduler

Zuvor haben wir die Mängel des OS-Schedulers bei der Planung relatierbarer Threads auf derselben CPU erörtert.

Obwohl es für Project Loom ein Ziel ist, steckbare Scheduler mit Glasfasern zuzulassen , wird ForkJoinPool im asynchronen Modus als Standard-Scheduler verwendet.

ForkJoinPool works on the work-stealing algorithm. Thus, every thread maintains a task deque and executes the task from its head. Furthermore, any idle thread does not block, waiting for the task and pulls it from the tail of another thread's deque instead.

The only difference in asynchronous mode is that the worker threads steal the task from the head of another deque.

ForkJoinPool adds a task scheduled by another running task to the local queue. Hence, executing it on the same CPU.

8. Conclusion

In this article, we discussed the problems in Java's current concurrency model and the changes proposed by Project Loom.

Dabei haben wir auch Aufgaben und Scheduler definiert und untersucht, wie Fibre und ForkJoinPool mithilfe von Kernel-Threads eine Alternative zu Java darstellen können.