Kursinhalt
Multithreading in Java
Multithreading in Java
ForkJoinPool
Die ForkJoinPool
-Klasse in Java für die Arbeit mit dem Fork/Join
-Framework ist genau die Umsetzung davon. Sie bietet Mechanismen zur Verwaltung von Aufgaben, die in kleinere Unteraufgaben unterteilt und parallel ausgeführt werden können.
Main
ForkJoinPool forkJoinPool = new ForkJoinPool();
ForkJoinPool
basiert auf dem Konzept von zwei grundlegenden Aktionen: fork und join. Fork
ist eine Aktion, bei der wir eine große Aufgabe in mehrere kleinere Teilaufgaben aufteilen, die parallel ausgeführt werden können. Join
ist der Prozess, bei dem die Ergebnisse dieser Teilaufgaben zusammengeführt werden, um das Ergebnis der ursprünglichen Aufgabe zu bilden.
Fallstudie
Stellen Sie sich vor, Sie müssen einen riesigen Datensatz analysieren, der in mehrere kleinere Sätze unterteilt werden kann. Wenn Sie jeden Datensatz separat verarbeiten und dann die Ergebnisse zusammenführen, können Sie die Verarbeitung erheblich beschleunigen, insbesondere auf Mehrprozessorsystemen.
Wie man ForkJoinPool verwendet
Es gibt 2 Klassen, um Aufgaben in ihnen zu implementieren, sie sind RecursiveTask
und RecursiveAction
. Beide erfordern eine abstrakte compute()
-Methode, die implementiert werden muss. In RecursiveTask
gibt die compute()
-Methode einen Wert zurück, während in RecursiveAction
die compute()
-Methode void zurückgibt.
Main
class ExampleTask extends RecursiveTask<String> { @Override protected String compute() { // code return null; } }
Wenn wir von einem RecursiveTask
erben, müssen wir sicherstellen, welchen Datentyp unsere compute()
-Methode zurückgeben wird, indem wir diese Syntax verwenden RecursiveTask<String>
.
Main
class ExampleAction extends RecursiveAction { @Override protected void compute() { // code } }
Hier ist es das Gegenteil, wir müssen den Typ in RecursiveAction
nicht explizit angeben, da unsere Methode nichts zurückgeben wird und nur die Aufgabe ausführt.
Starten einer Aufgabe
Übrigens können wir eine Aufgabe zur Ausführung starten, ohne ForkJoinPool
zu verwenden, indem wir einfach die Methoden fork()
und join()
verwenden.
Es ist wichtig zu beachten, dass die Methode fork()
die Aufgabe an einen Thread sendet. Die Methode join()
wird verwendet, um das Ergebnis zu erhalten.
Main
public static void main(String[] args) { ExampleTask simpleClass = new ExampleTask(); simpleClass.fork(); System.out.println(simpleClass.join()); }
Hauptmethoden von ForkJoinPool
invoke()
: Startet eine Aufgabe im Pool und wartet auf deren Abschluss. Gibt das Ergebnis des Abschlusses der Aufgabe zurück;submit()
: Reicht eine Aufgabe an den Pool ein, blockiert jedoch nicht den aktuellen Thread, während auf den Abschluss der Aufgabe gewartet wird. Kann verwendet werden, um Aufgaben einzureichen und derenFuture
-Objekte abzurufen;execute()
: Führt eine Aufgabe im Pool aus, gibt jedoch kein Ergebnis zurück und blockiert nicht den aktuellen Thread.
Wie können wir eine Aufgabe mit ForkJoinPool
starten, ganz einfach!
Main
public static void main(String[] args) { ExampleAction simpleClass = new ExampleAction(); ForkJoinPool forkJoinPool = new ForkJoinPool(); System.out.println(forkJoinPool.invoke(simpleClass)); }
Aber was ist der Unterschied zwischen dem Ausführen von fork()
, join()
und über die ForkJoinPool
Klasse?
Es ist sehr einfach! Wenn wir die erste Methode mit Hilfe der fork()
, join()
Methoden verwenden, starten wir die Aufgabe im selben Thread, in dem diese Methoden aufgerufen wurden, blockieren diesen Thread, während wir mit Hilfe der ForkJoinPool
Klasse einen Thread aus dem Pool zugewiesen bekommen und dieser in diesem Thread arbeitet, ohne den Hauptthread zu blockieren.
Schauen wir uns das genauer an, nehmen wir an, wir haben eine solche Implementierung:
Main
class ExampleRecursiveTask extends RecursiveTask<String> { @Override protected String compute() { System.out.println("Thread: " + Thread.currentThread().getName()); return "Wow, it works!!!"; } }
Und wir möchten diesen Code unter Verwendung von fork()
und join()
ausführen. Lassen Sie uns sehen, was auf die Konsole gedruckt wird und welcher Thread diese Aufgabe ausführen wird.
Main
public class Main { public static void main(String[] args) { ExampleRecursiveTask simpleClass = new ExampleRecursiveTask(); simpleClass.fork(); System.out.println(simpleClass.join()); } }
Und wir erhalten diese Ausgabe auf der Konsole:
Lassen Sie uns nun überprüfen, was passiert, wenn wir mit ForkJoinPool
ausführen:
Main
public class Main { public static void main(String[] args) { ExampleRecursiveTask simpleClass = new ExampleRecursiveTask(); ForkJoinPool forkJoinPool = new ForkJoinPool(); System.out.println(forkJoinPool.invoke(simpleClass)); } }
Und wir kommen zu diesem Schluss:
Und wie Sie sehen können, ist der Unterschied, dass bei Verwendung der 1. Methode die Aufgabe im selben Thread ausgeführt wird, der diese Aufgabe aufgerufen hat (Hauptthread). Aber wenn wir die 2. Methode verwenden, wird der Thread aus dem ForkJoinPool
-Thread-Pool entnommen und der Hauptthread, der diese Logik aufgerufen hat, wird nicht blockiert und läuft weiter!
Wie man Fork/Join im Code implementiert
Der einfachste Weg, dies zu erklären, wäre in einem Video, anstatt Ihnen 50-80 Zeilen Code zu geben und es Punkt für Punkt zu erklären.
Zusammenfassung
ForkJoinPool
ist effektiv für Aufgaben, die leicht in kleinere Unteraufgaben unterteilt werden können. Wenn die Aufgaben jedoch zu klein sind, kann die Verwendung vonForkJoinPool
keinen signifikanten Leistungsgewinn bringen.
Danke für Ihr Feedback!