Kursinhalt
Multithreading in Java
Multithreading in Java
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.
Main
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.
Main
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.
Main
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.
Main
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.
Main
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.
Main
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.
Danke für Ihr Feedback!