Executoren und Thread-Pool
Wir haben bereits eine Vielzahl von Mechanismen zur Unterstützung von Multithreading untersucht, und Executors ist einer davon!
Was sind Executors und Thread-Pooling?
Executors ist ein Mechanismus, der Abstraktionen auf hoher Ebene für die Verwaltung von Threads bereitstellt. Er ermöglicht das Erstellen und Verwalten eines Thread-Pools, der aus einer Gruppe von vorgefertigten 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 auf die Threads verteilt wird.
Was genau ist also ein Thread-Pool? Es handelt sich um eine Sammlung von vorgefertigten Threads, die bereit sind, Aufgaben auszuführen. Durch die Verwendung eines Thread-Pools wird der Overhead des ständigen Erstellens und Entfernens von Threads vermieden, da dieselben Threads für mehrere Aufgaben wiederverwendet werden können.
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 übernommen, und sobald die Aufgabe abgeschlossen ist, übernimmt der Thread eine neue Aufgabe aus der Warteschlange. Sobald alle Aufgaben in der Warteschlange erledigt sind, bleiben die Threads aktiv und warten auf neue Aufgaben.
Beispiel aus dem Alltag
Stellen Sie sich ein Restaurant vor, in dem Köche (Threads) Bestellungen (Aufgaben) zubereiten. Anstatt für jede Bestellung einen neuen Koch einzustellen, beschäftigt das Restaurant eine begrenzte Anzahl an Köchen, die die Bestellungen nacheinander bearbeiten. Sobald ein Koch eine Bestellung abgeschlossen hat, übernimmt er die nächste, was eine effiziente Nutzung der Ressourcen des Restaurants ermöglicht.
Hauptmethode
newFixedThreadPool(int n): Erstellt einen Pool mit einer festen Anzahl von Threads, die n entspricht.
Main.java
1ExecutorService executorService = Executors.newFixedThreadPool(20);
newCachedThreadPool(): Erstellt einen Pool, der bei Bedarf neue Threads erzeugen kann, aber vorhandene Threads wiederverwendet, sofern verfügbar.
Main.java
1ExecutorService executorService = Executors.newCachedThreadPool();
newSingleThreadExecutor(): Erstellt einen Single-Thread-Pool, der sicherstellt, dass Aufgaben sequenziell ausgeführt werden, also nacheinander. Geeignet für Aufgaben, die in strikter Reihenfolge ausgeführt werden müssen.
Main.java
1ExecutorService executorService = Executors.newSingleThreadExecutor();
In allen Beispielen geben die Methoden von Executors eine Implementierung des ExecutorService-Interfaces zurück, die zur Verwaltung von Threads verwendet wird.
ExecutorService stellt Methoden zur Verwaltung eines Thread-Pools bereit. Zum Beispiel akzeptiert submit(Runnable task) eine Aufgabe als Runnable-Objekt und platziert sie in eine Warteschlange zur Ausführung. Es gibt ein Future-Objekt zurück, mit dem der Status der Aufgabe überprüft und ein Ergebnis abgerufen werden kann, falls die Aufgabe ein Ergebnis liefert.
Main.java
12345678910111213141516171819202122232425262728293031323334package 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, führt aber die aktuellen Aufgaben zu Ende. Nach dem Aufruf dieser Methode kann der Pool nicht 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 sicherstellt, dass alle Aufgaben vor dem endgültigen Abschluss des Pools beendet sind.
Außerdem wurde das wichtigste Interface zur Überwachung des Thread-Zustands noch nicht erwähnt: das Future-Interface. Die submit()-Methode des ExecutorService-Interfaces gibt eine Implementierung des Future-Interfaces zurück.
Um das Ergebnis der Thread-Ausführung zu erhalten, kann die Methode get() verwendet werden. Wenn der Thread Runnable implementiert, liefert die Methode get() keinen Wert zurück. Bei Callable<T> hingegen wird ein Wert vom Typ T zurückgegeben.
Main.java
12345678910111213141516171819202122232425262728293031package 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(); } }
Mit der Methode cancel(boolean mayInterruptIfRunning) kann versucht werden, die Ausführung einer Aufgabe zu abbrechen. Falls die Aufgabe noch nicht gestartet wurde, wird sie abgebrochen. Läuft die Aufgabe bereits, kann sie je nach Wert des Flags mayInterruptIfRunning unterbrochen werden.
true: Falls die Aufgabe läuft, wird sie durch Aufruf von Thread.interrupt() im ausführenden Thread unterbrochen.
false: Falls die Aufgabe läuft, wird sie nicht unterbrochen und der Abbruchversuch hat keine Auswirkung auf die aktuell laufende Aufgabe.
Sowie 2 Methoden, deren Funktion sich intuitiv erschließt:
isCancelled(): Prüft, ob die Aufgabe abgebrochen wurde;isDone(): Prüft, ob die Aufgabe abgeschlossen wurde.
Anwendungsbeispiel
Die Größe des Threadpools hängt von der Art der auszuführenden Aufgaben ab. In der Regel sollte die Größe des Threadpools nicht fest codiert werden, sondern anpassbar sein. Die optimale Größe wird durch Überwachung des Durchsatzes der ausgeführten Aufgaben bestimmt.
Es ist maximal effizient, die Anzahl der threads = processor cores zu verwenden. Dies kann im Code mit Runtime.getRuntime().availableProcessors() eingesehen werden.
Main.java
1int availableProcessors = Runtime.getRuntime().availableProcessors();
Unterschiede zwischen direkter Thread-Erstellung und der Verwendung von ExecutorService
Die Hauptunterschiede zwischen der direkten Erstellung von Threads und der Verwendung von ExecutorService liegen in der Bequemlichkeit und dem Ressourcenmanagement. Die manuelle Erstellung von Threads erfordert die individuelle Verwaltung jedes Threads, was den Code und die Administration erschwert.
ExecutorService vereinfacht die Verwaltung durch die Nutzung eines Thread-Pools, wodurch die Aufgabenbearbeitung erleichtert wird. Während die manuelle Thread-Erstellung zu einem hohen Ressourcenverbrauch führen kann, ermöglicht ExecutorService die Anpassung der Größe des Thread-Pools.
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
Executoren und Thread-Pool
Swipe um das Menü anzuzeigen
Wir haben bereits eine Vielzahl von Mechanismen zur Unterstützung von Multithreading untersucht, und Executors ist einer davon!
Was sind Executors und Thread-Pooling?
Executors ist ein Mechanismus, der Abstraktionen auf hoher Ebene für die Verwaltung von Threads bereitstellt. Er ermöglicht das Erstellen und Verwalten eines Thread-Pools, der aus einer Gruppe von vorgefertigten 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 auf die Threads verteilt wird.
Was genau ist also ein Thread-Pool? Es handelt sich um eine Sammlung von vorgefertigten Threads, die bereit sind, Aufgaben auszuführen. Durch die Verwendung eines Thread-Pools wird der Overhead des ständigen Erstellens und Entfernens von Threads vermieden, da dieselben Threads für mehrere Aufgaben wiederverwendet werden können.
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 übernommen, und sobald die Aufgabe abgeschlossen ist, übernimmt der Thread eine neue Aufgabe aus der Warteschlange. Sobald alle Aufgaben in der Warteschlange erledigt sind, bleiben die Threads aktiv und warten auf neue Aufgaben.
Beispiel aus dem Alltag
Stellen Sie sich ein Restaurant vor, in dem Köche (Threads) Bestellungen (Aufgaben) zubereiten. Anstatt für jede Bestellung einen neuen Koch einzustellen, beschäftigt das Restaurant eine begrenzte Anzahl an Köchen, die die Bestellungen nacheinander bearbeiten. Sobald ein Koch eine Bestellung abgeschlossen hat, übernimmt er die nächste, was eine effiziente Nutzung der Ressourcen des Restaurants ermöglicht.
Hauptmethode
newFixedThreadPool(int n): Erstellt einen Pool mit einer festen Anzahl von Threads, die n entspricht.
Main.java
1ExecutorService executorService = Executors.newFixedThreadPool(20);
newCachedThreadPool(): Erstellt einen Pool, der bei Bedarf neue Threads erzeugen kann, aber vorhandene Threads wiederverwendet, sofern verfügbar.
Main.java
1ExecutorService executorService = Executors.newCachedThreadPool();
newSingleThreadExecutor(): Erstellt einen Single-Thread-Pool, der sicherstellt, dass Aufgaben sequenziell ausgeführt werden, also nacheinander. Geeignet für Aufgaben, die in strikter Reihenfolge ausgeführt werden müssen.
Main.java
1ExecutorService executorService = Executors.newSingleThreadExecutor();
In allen Beispielen geben die Methoden von Executors eine Implementierung des ExecutorService-Interfaces zurück, die zur Verwaltung von Threads verwendet wird.
ExecutorService stellt Methoden zur Verwaltung eines Thread-Pools bereit. Zum Beispiel akzeptiert submit(Runnable task) eine Aufgabe als Runnable-Objekt und platziert sie in eine Warteschlange zur Ausführung. Es gibt ein Future-Objekt zurück, mit dem der Status der Aufgabe überprüft und ein Ergebnis abgerufen werden kann, falls die Aufgabe ein Ergebnis liefert.
Main.java
12345678910111213141516171819202122232425262728293031323334package 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, führt aber die aktuellen Aufgaben zu Ende. Nach dem Aufruf dieser Methode kann der Pool nicht 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 sicherstellt, dass alle Aufgaben vor dem endgültigen Abschluss des Pools beendet sind.
Außerdem wurde das wichtigste Interface zur Überwachung des Thread-Zustands noch nicht erwähnt: das Future-Interface. Die submit()-Methode des ExecutorService-Interfaces gibt eine Implementierung des Future-Interfaces zurück.
Um das Ergebnis der Thread-Ausführung zu erhalten, kann die Methode get() verwendet werden. Wenn der Thread Runnable implementiert, liefert die Methode get() keinen Wert zurück. Bei Callable<T> hingegen wird ein Wert vom Typ T zurückgegeben.
Main.java
12345678910111213141516171819202122232425262728293031package 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(); } }
Mit der Methode cancel(boolean mayInterruptIfRunning) kann versucht werden, die Ausführung einer Aufgabe zu abbrechen. Falls die Aufgabe noch nicht gestartet wurde, wird sie abgebrochen. Läuft die Aufgabe bereits, kann sie je nach Wert des Flags mayInterruptIfRunning unterbrochen werden.
true: Falls die Aufgabe läuft, wird sie durch Aufruf von Thread.interrupt() im ausführenden Thread unterbrochen.
false: Falls die Aufgabe läuft, wird sie nicht unterbrochen und der Abbruchversuch hat keine Auswirkung auf die aktuell laufende Aufgabe.
Sowie 2 Methoden, deren Funktion sich intuitiv erschließt:
isCancelled(): Prüft, ob die Aufgabe abgebrochen wurde;isDone(): Prüft, ob die Aufgabe abgeschlossen wurde.
Anwendungsbeispiel
Die Größe des Threadpools hängt von der Art der auszuführenden Aufgaben ab. In der Regel sollte die Größe des Threadpools nicht fest codiert werden, sondern anpassbar sein. Die optimale Größe wird durch Überwachung des Durchsatzes der ausgeführten Aufgaben bestimmt.
Es ist maximal effizient, die Anzahl der threads = processor cores zu verwenden. Dies kann im Code mit Runtime.getRuntime().availableProcessors() eingesehen werden.
Main.java
1int availableProcessors = Runtime.getRuntime().availableProcessors();
Unterschiede zwischen direkter Thread-Erstellung und der Verwendung von ExecutorService
Die Hauptunterschiede zwischen der direkten Erstellung von Threads und der Verwendung von ExecutorService liegen in der Bequemlichkeit und dem Ressourcenmanagement. Die manuelle Erstellung von Threads erfordert die individuelle Verwaltung jedes Threads, was den Code und die Administration erschwert.
ExecutorService vereinfacht die Verwaltung durch die Nutzung eines Thread-Pools, wodurch die Aufgabenbearbeitung erleichtert wird. Während die manuelle Thread-Erstellung zu einem hohen Ressourcenverbrauch führen kann, ermöglicht ExecutorService die Anpassung der Größe des Thread-Pools.
Danke für Ihr Feedback!