Notice: This page requires JavaScript to function properly.
Please enable JavaScript in your browser settings or update your browser.
Lernen Executors und Thread-Pool | Hochrangige Synchronisationsmechanismen
Multithreading in Java
course content

Kursinhalt

Multithreading in Java

Multithreading in Java

1. Grundlagen der Multithread-Verarbeitung
2. Synchronisierte Sammlungen
3. Hochrangige Synchronisationsmechanismen
4. Best Practices für Multithreading

book
Executors und Thread-Pool

Wir haben bereits eine Vielzahl von Mechanismen zur Unterstützung von Multithreading erkundet, und Executors ist einer davon!

Was sind Executors und Thread-Pooling?

Executors ist ein Mechanismus, der High-Level-Abstraktionen für die Handhabung von Threads bietet. Er ermöglicht es Ihnen, einen Thread-Pool zu erstellen und zu verwalten, der aus einer Reihe von vorhandenen Threads besteht, die bereit sind, Aufgaben auszuführen. Anstatt für jede Aufgabe einen neuen Thread zu erstellen, werden Aufgaben an den Pool gesendet, wo ihre Ausführung unter den Threads verteilt wird.

Was genau ist ein Thread-Pool? Es ist eine Sammlung von bereits vorhandenen Threads, die bereit sind, Aufgaben auszuführen. Durch die Verwendung eines Thread-Pools vermeiden Sie den Aufwand des Erstellens und Zerstörens von Threads wiederholt, da dieselben Threads für mehrere Aufgaben wiederverwendet werden können.

Hinweis

Wenn es mehr Aufgaben als Threads gibt, warten die Aufgaben in der Task Queue. Eine Aufgabe aus der Warteschlange wird von einem verfügbaren Thread aus dem Pool bearbeitet, und sobald die Aufgabe abgeschlossen ist, nimmt der Thread eine neue Aufgabe aus der Warteschlange auf. Sobald alle Aufgaben in der Warteschlange abgeschlossen sind, bleiben die Threads aktiv und warten auf neue Aufgaben.

Beispiel aus dem Leben

Denken Sie an ein Restaurant, in dem Köche (Threads) Bestellungen (Aufgaben) zubereiten. Anstatt für jede Bestellung einen neuen Koch einzustellen, beschäftigt das Restaurant eine begrenzte Anzahl von Köchen, die die Bestellungen bearbeiten, sobald sie eingehen. Sobald ein Koch eine Bestellung fertiggestellt hat, übernimmt er die nächste, was dazu beiträgt, die Ressourcen des Restaurants effizient zu nutzen.

Hauptmethode

newFixedThreadPool(int n): Erstellt einen Pool mit einer festen Anzahl von Threads, die n entspricht.

java

Main

copy
1
ExecutorService executorService = Executors.newFixedThreadPool(20);

newCachedThreadPool(): Erstellt einen Pool, der bei Bedarf neue Threads erstellen kann, aber verfügbare Threads wiederverwendet, wenn welche vorhanden sind.

java

Main

copy
1
ExecutorService executorService = Executors.newCachedThreadPool();

newSingleThreadExecutor(): Erstellt einen einzelnen Thread-Pool, der sicherstellt, dass Aufgaben nacheinander ausgeführt werden, also eine nach der anderen. Dies ist nützlich für Aufgaben, die in strikter Reihenfolge ausgeführt werden müssen.

java

Main

copy
1
ExecutorService executorService = Executors.newSingleThreadExecutor();

In allen Beispielen geben die Methoden von Executors eine Implementierung der ExecutorService-Schnittstelle zurück, die zur Verwaltung von Threads verwendet wird.

ExecutorService bietet Methoden zur Verwaltung eines Thread-Pools. Zum Beispiel akzeptiert submit(Runnable task) eine Aufgabe als Runnable-Objekt und platziert sie in einer Warteschlange zur Ausführung. Es gibt ein Future-Objekt zurück, das verwendet werden kann, um den Status der Aufgabe zu überprüfen und ein Ergebnis zu erhalten, wenn die Aufgabe ein Ergebnis liefert.

java

Main

copy
12345678910111213141516171819202122232425262728293031323334
package com.example; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; public class Main { public static void main(String[] args) { // Create a thread pool with 5 threads ExecutorService executor = Executors.newFixedThreadPool(5); // Define the task to be executed Runnable task = () -> { System.out.println("Task is running: " + Thread.currentThread().getName()); try { Thread.sleep(2000); // Simulate some work } catch (InterruptedException e) { Thread.currentThread().interrupt(); } System.out.println("Task completed: " + Thread.currentThread().getName()); }; // Submit the task for execution and get a `Future` Future<?> future = executor.submit(task); // Check if the task is done System.out.println("Is task done? " + future.isDone()); // You can use `future` to check the status of the task or wait for its completion // Example: future.get() - blocks until the task is completed (not used in this example) // Initiate an orderly shutdown of the executor service executor.shutdown(); } }

Die Methode shutdown() startet ein geordnetes Herunterfahren des Thread-Pools. Sie akzeptiert keine neuen Aufgaben mehr, wird aber die aktuellen Aufgaben abschließen. Sobald Sie diese Methode aufrufen, kann der Pool nicht mehr neu gestartet werden.

Die Methode awaitTermination(long timeout, TimeUnit unit) wartet darauf, dass alle Aufgaben im Pool innerhalb des angegebenen Zeitrahmens abgeschlossen werden. Dies ist ein blockierendes Warten, das es Ihnen ermöglicht, sicherzustellen, dass alle Aufgaben vor der endgültigen Beendigung des Pools abgeschlossen sind.

Außerdem haben wir nicht das Haupt-Interface erwähnt, das hilft, den Zustand des Threads zu verfolgen, es ist das Future-Interface. Die submit()-Methode des ExecutorService-Interfaces gibt eine Implementierung des Future-Interfaces zurück.

Wenn Sie das Ergebnis der Thread-Ausführung erhalten möchten, können Sie die get()-Methode verwenden. Wenn der Thread Runnable implementiert, gibt die get()-Methode nichts zurück, aber wenn Callable<T>, gibt sie den Typ T zurück.

java

Main

copy
12345678910111213141516171819202122232425262728293031
package com.example; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; public class Main { public static void main(String[] args) { ExecutorService executor = Executors.newFixedThreadPool(3); // Callable task that returns a result Callable<String> task = () -> { Thread.sleep(1000); // Simulate some work return "Task result"; }; Future<String> future = executor.submit(task); try { // Get the result of the task String result = future.get(); System.out.println("Task completed with result: " + result); } catch (InterruptedException | ExecutionException e) { e.printStackTrace(); } executor.shutdown(); } }

Sie können auch die cancel(boolean mayInterruptIfRunning) Methode verwenden, um zu versuchen, die Ausführung einer Aufgabe zu abbrechen. Wenn die Aufgabe noch nicht gestartet wurde, wird sie abgebrochen. Wenn die Aufgabe bereits läuft, kann sie basierend auf dem mayInterruptIfRunning-Flag unterbrochen werden.

true: Wenn die Aufgabe läuft, wird sie durch Aufrufen von Thread.interrupt() im ausführenden Thread unterbrochen. false: Wenn die Aufgabe läuft, wird sie nicht unterbrochen, und der Abbruchversuch hat keine Auswirkung auf die derzeit laufende Aufgabe.

Nun und 2 Methoden, die intuitiv verstehen, was sie tun:

  • isCancelled(): Überprüft, ob die Aufgabe abgebrochen wurde;
  • isDone(): Überprüft, ob die Aufgabe abgeschlossen wurde.

Beispiel der Verwendung

Es ist maximal effizient, die Anzahl der threads = processor cores zu verwenden. Sie können dies im Code mit Runtime.getRuntime().availableProcessors() sehen.

java

Main

copy
1
int availableProcessors = Runtime.getRuntime().availableProcessors();

Unterschiede zwischen der direkten Erstellung von Threads und der Verwendung von ExecutorService

Die Hauptunterschiede zwischen der direkten Erstellung von Threads und der Verwendung von ExecutorService sind Bequemlichkeit und Ressourcenmanagement. Die manuelle Erstellung von Threads erfordert die Verwaltung jedes einzelnen Threads, was den Code und die Verwaltung kompliziert.

ExecutorService vereinfacht das Management durch die Verwendung eines Thread-Pools, was die Aufgabenbearbeitung erleichtert. Darüber hinaus kann die manuelle Thread-Erstellung zu einem hohen Ressourcenverbrauch führen, während ExecutorService es Ihnen ermöglicht, die Größe des Thread-Pools anzupassen.

War alles klar?

Wie können wir es verbessern?

Danke für Ihr Feedback!

Abschnitt 3. Kapitel 6
We're sorry to hear that something went wrong. What happened?
some-alt