Fork-Join-Pool
Die Klasse ForkJoinPool in Java zur Arbeit mit dem Fork/Join-Framework ist genau die Umsetzung davon. Sie bietet Mechanismen zur Verwaltung von Aufgaben, die in kleinere Teilaufgaben unterteilt und parallel ausgeführt werden können.
Main.java
1ForkJoinPool forkJoinPool = new ForkJoinPool();
ForkJoinPool basiert auf dem Konzept von zwei grundlegenden Aktionen: fork und join. Fork ist eine Aktion, bei der eine große Aufgabe in mehrere kleinere Teilaufgaben unterteilt wird, 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 Mengen unterteilt werden kann. Wenn Sie jeden Datensatz separat verarbeiten und anschließend die Ergebnisse zusammenführen, können Sie die Verarbeitung erheblich beschleunigen, insbesondere auf Multiprozessorsystemen.
Verwendung von ForkJoinPool
Es gibt 2 Klassen zur Implementierung von Aufgaben: RecursiveTask und RecursiveAction. Beide erfordern die Implementierung der abstrakten Methode compute(). Bei RecursiveTask gibt die Methode compute() einen Wert zurück, während sie bei RecursiveAction die Methode compute() void zurückgibt.
Main.java
12345678class ExampleTask extends RecursiveTask<String> { @Override protected String compute() { // code return null; } }
Wenn wir von einer RecursiveTask erben, müssen wir sicherstellen, dass wir den Datentyp, den unsere compute()-Methode zurückgeben wird, mit dieser Syntax angeben: RecursiveTask<String>.
Main.java
1234567class ExampleAction extends RecursiveAction { @Override protected void compute() { // code } }
Hier ist es umgekehrt: Wir müssen den Typ in RecursiveAction nicht explizit angeben, da unsere Methode nichts zurückgeben wird und lediglich die Aufgabe ausführt.
Starten einer Aufgabe
Übrigens kann eine Aufgabe zur Ausführung gestartet werden, ohne dass ForkJoinPool verwendet wird, indem lediglich die Methoden fork() und join() genutzt werden.
Es ist wichtig zu beachten, dass die Methode fork() die Aufgabe an einen Thread übergibt. Die Methode join() wird verwendet, um das Ergebnis zu erhalten.
Main.java
12345public 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 der abgeschlossenen Aufgabe zurück;submit(): Übermittelt eine Aufgabe an den Pool, blockiert jedoch den aktuellen Thread nicht, während auf den Abschluss der Aufgabe gewartet wird. Kann verwendet werden, um Aufgaben zu übermitteln und derenFuture-Objekte abzurufen;execute(): Führt eine Aufgabe im Pool aus, gibt jedoch kein Ergebnis zurück und blockiert den aktuellen Thread nicht.
Wie kann eine Aufgabe mit ForkJoinPool gestartet werden? Sehr einfach!
Main.java
12345public 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 der Ausführung mit fork(), join() und über die ForkJoinPool Klasse?
Es ist ganz einfach! Bei der ersten Methode mit den Methoden fork(), join() wird die Aufgabe im selben Thread gestartet, in dem diese Methoden aufgerufen wurden, wodurch dieser Thread blockiert wird. Mit der Klasse ForkJoinPool hingegen wird ein Thread aus dem Pool zugewiesen und die Aufgabe läuft in diesem Thread, ohne den Hauptthread zu blockieren.
Betrachten wir dies genauer, nehmen wir an, wir haben eine solche Implementierung:
Main.java
12345678class 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 mithilfe von fork() und join() ausführen. Sehen wir uns an, was in der Konsole ausgegeben wird und welcher Thread diese Aufgabe ausführt.
Main.java
1234567public 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:
Thread: main
Wow, it works!!!
Sehen wir uns nun an, was passiert, wenn wir mit ForkJoinPool ausführen:
Main.java
1234567public 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 folgendem Fazit:
Thread: ForkJoinPool-1-worker-1
Wow, it works!!!
Wie ersichtlich ist, besteht der Unterschied darin, dass bei Verwendung der ersten Methode die Aufgabe im selben Thread ausgeführt wird, der diese Aufgabe aufgerufen hat (Main-Thread). Bei Nutzung der zweiten Methode hingegen wird der Thread aus dem ForkJoinPool-Threadpool entnommen und der Hauptthread, der diese Logik aufgerufen hat, wird nicht blockiert und läuft weiter!
Implementierung von Fork/Join im Code
Der einfachste Weg, dies zu erklären, wäre in einem Video, anstatt Ihnen 50-80 Zeilen Code zu geben und diese Schritt für Schritt zu erläutern.
ForkJoinPool ist effektiv für Aufgaben, die sich leicht in kleinere Teilaufgaben unterteilen lassen. Sind die Aufgaben jedoch zu klein, bringt die Verwendung von ForkJoinPool möglicherweise keinen nennenswerten Leistungsgewinn.
Danke für Ihr Feedback!
Fragen Sie AI
Fragen Sie AI
Fragen Sie alles oder probieren Sie eine der vorgeschlagenen Fragen, um unser Gespräch zu beginnen
Awesome!
Completion rate improved to 3.33
Fork-Join-Pool
Swipe um das Menü anzuzeigen
Die Klasse ForkJoinPool in Java zur Arbeit mit dem Fork/Join-Framework ist genau die Umsetzung davon. Sie bietet Mechanismen zur Verwaltung von Aufgaben, die in kleinere Teilaufgaben unterteilt und parallel ausgeführt werden können.
Main.java
1ForkJoinPool forkJoinPool = new ForkJoinPool();
ForkJoinPool basiert auf dem Konzept von zwei grundlegenden Aktionen: fork und join. Fork ist eine Aktion, bei der eine große Aufgabe in mehrere kleinere Teilaufgaben unterteilt wird, 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 Mengen unterteilt werden kann. Wenn Sie jeden Datensatz separat verarbeiten und anschließend die Ergebnisse zusammenführen, können Sie die Verarbeitung erheblich beschleunigen, insbesondere auf Multiprozessorsystemen.
Verwendung von ForkJoinPool
Es gibt 2 Klassen zur Implementierung von Aufgaben: RecursiveTask und RecursiveAction. Beide erfordern die Implementierung der abstrakten Methode compute(). Bei RecursiveTask gibt die Methode compute() einen Wert zurück, während sie bei RecursiveAction die Methode compute() void zurückgibt.
Main.java
12345678class ExampleTask extends RecursiveTask<String> { @Override protected String compute() { // code return null; } }
Wenn wir von einer RecursiveTask erben, müssen wir sicherstellen, dass wir den Datentyp, den unsere compute()-Methode zurückgeben wird, mit dieser Syntax angeben: RecursiveTask<String>.
Main.java
1234567class ExampleAction extends RecursiveAction { @Override protected void compute() { // code } }
Hier ist es umgekehrt: Wir müssen den Typ in RecursiveAction nicht explizit angeben, da unsere Methode nichts zurückgeben wird und lediglich die Aufgabe ausführt.
Starten einer Aufgabe
Übrigens kann eine Aufgabe zur Ausführung gestartet werden, ohne dass ForkJoinPool verwendet wird, indem lediglich die Methoden fork() und join() genutzt werden.
Es ist wichtig zu beachten, dass die Methode fork() die Aufgabe an einen Thread übergibt. Die Methode join() wird verwendet, um das Ergebnis zu erhalten.
Main.java
12345public 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 der abgeschlossenen Aufgabe zurück;submit(): Übermittelt eine Aufgabe an den Pool, blockiert jedoch den aktuellen Thread nicht, während auf den Abschluss der Aufgabe gewartet wird. Kann verwendet werden, um Aufgaben zu übermitteln und derenFuture-Objekte abzurufen;execute(): Führt eine Aufgabe im Pool aus, gibt jedoch kein Ergebnis zurück und blockiert den aktuellen Thread nicht.
Wie kann eine Aufgabe mit ForkJoinPool gestartet werden? Sehr einfach!
Main.java
12345public 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 der Ausführung mit fork(), join() und über die ForkJoinPool Klasse?
Es ist ganz einfach! Bei der ersten Methode mit den Methoden fork(), join() wird die Aufgabe im selben Thread gestartet, in dem diese Methoden aufgerufen wurden, wodurch dieser Thread blockiert wird. Mit der Klasse ForkJoinPool hingegen wird ein Thread aus dem Pool zugewiesen und die Aufgabe läuft in diesem Thread, ohne den Hauptthread zu blockieren.
Betrachten wir dies genauer, nehmen wir an, wir haben eine solche Implementierung:
Main.java
12345678class 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 mithilfe von fork() und join() ausführen. Sehen wir uns an, was in der Konsole ausgegeben wird und welcher Thread diese Aufgabe ausführt.
Main.java
1234567public 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:
Thread: main
Wow, it works!!!
Sehen wir uns nun an, was passiert, wenn wir mit ForkJoinPool ausführen:
Main.java
1234567public 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 folgendem Fazit:
Thread: ForkJoinPool-1-worker-1
Wow, it works!!!
Wie ersichtlich ist, besteht der Unterschied darin, dass bei Verwendung der ersten Methode die Aufgabe im selben Thread ausgeführt wird, der diese Aufgabe aufgerufen hat (Main-Thread). Bei Nutzung der zweiten Methode hingegen wird der Thread aus dem ForkJoinPool-Threadpool entnommen und der Hauptthread, der diese Logik aufgerufen hat, wird nicht blockiert und läuft weiter!
Implementierung von Fork/Join im Code
Der einfachste Weg, dies zu erklären, wäre in einem Video, anstatt Ihnen 50-80 Zeilen Code zu geben und diese Schritt für Schritt zu erläutern.
ForkJoinPool ist effektiv für Aufgaben, die sich leicht in kleinere Teilaufgaben unterteilen lassen. Sind die Aufgaben jedoch zu klein, bringt die Verwendung von ForkJoinPool möglicherweise keinen nennenswerten Leistungsgewinn.
Danke für Ihr Feedback!