Notice: This page requires JavaScript to function properly.
Please enable JavaScript in your browser settings or update your browser.
Lernen Fork-Join-Pool | Multithreading Best Practices
Multithreading in Java

bookFork-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

Main.java

copy
1
ForkJoinPool 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

Main.java

copy
12345678
class 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

Main.java

copy
1234567
class 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

Main.java

copy
12345
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 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 deren Future-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

Main.java

copy
12345
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 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

Main.java

copy
12345678
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 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

Main.java

copy
1234567
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:

Thread: main
Wow, it works!!!

Sehen wir uns nun an, was passiert, wenn wir mit ForkJoinPool ausführen:

Main.java

Main.java

copy
1234567
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 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.

Note
Hinweis

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.

War alles klar?

Wie können wir es verbessern?

Danke für Ihr Feedback!

Abschnitt 4. Kapitel 2

Fragen Sie AI

expand

Fragen Sie AI

ChatGPT

Fragen Sie alles oder probieren Sie eine der vorgeschlagenen Fragen, um unser Gespräch zu beginnen

Awesome!

Completion rate improved to 3.33

bookFork-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

Main.java

copy
1
ForkJoinPool 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

Main.java

copy
12345678
class 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

Main.java

copy
1234567
class 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

Main.java

copy
12345
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 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 deren Future-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

Main.java

copy
12345
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 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

Main.java

copy
12345678
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 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

Main.java

copy
1234567
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:

Thread: main
Wow, it works!!!

Sehen wir uns nun an, was passiert, wenn wir mit ForkJoinPool ausführen:

Main.java

Main.java

copy
1234567
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 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.

Note
Hinweis

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.

War alles klar?

Wie können wir es verbessern?

Danke für Ihr Feedback!

Abschnitt 4. Kapitel 2
some-alt