Java Language
Executor-, ExecutorService- und Thread-Pools
Suche…
Einführung
Die Executor- Schnittstelle in Java bietet die Möglichkeit, die Aufgabe von Aufgaben von den Mechanismen zu koppeln, wie die einzelnen Aufgaben ausgeführt werden, einschließlich Details zur Verwendung des Threads, zur Planung usw. Ein Executor wird normalerweise verwendet, anstatt Threads explizit zu erstellen. Mit Executors müssen Entwickler ihren Code nicht wesentlich umschreiben, um die Task-Ausführungsrichtlinie ihres Programms einfach anpassen zu können.
Bemerkungen
Fallstricke
- Wenn Sie eine Task für die wiederholte Ausführung planen, wird Ihre Task je nach verwendetem ScheduledExecutorService möglicherweise vor jeder weiteren Ausführung angehalten, wenn die Ausführung Ihrer Task eine Ausnahme verursacht, die nicht behandelt wird. Siehe Mutter F ** k beim ScheduledExecutorService!
Feuer und Vergessen - ausführbare Aufgaben
Executoren akzeptieren eine java.lang.Runnable
die (möglicherweise rechnerisch oder anderweitig andauernd oder stark) Code enthält, der in einem anderen Thread ausgeführt werden soll.
Verwendung wäre:
Executor exec = anExecutor;
exec.execute(new Runnable() {
@Override public void run() {
//offloaded work, no need to get result back
}
});
Beachten Sie, dass Sie mit diesem Executor keine Möglichkeit haben, einen berechneten Wert zurückzubekommen.
Mit Java 8 kann man Lambdas verwenden, um das Codebeispiel zu verkürzen.
Executor exec = anExecutor;
exec.execute(() -> {
//offloaded work, no need to get result back
});
ThreadPoolExecutor
Ein häufig verwendeter Executor ist der ThreadPoolExecutor
, der sich um die Thread-Behandlung kümmert. Sie können die minimale Anzahl an Threads konfigurieren, die der Executor immer beibehalten muss, wenn nicht viel zu tun ist (Core-Größe genannt) und eine maximale Thread-Größe, auf die der Pool wachsen kann, wenn mehr Arbeit zu erledigen ist. Wenn die Arbeitslast nachlässt, verringert der Pool die Thread-Anzahl langsam wieder, bis die Mindestgröße erreicht ist.
ThreadPoolExecutor pool = new ThreadPoolExecutor(
1, // keep at least one thread ready,
// even if no Runnables are executed
5, // at most five Runnables/Threads
// executed in parallel
1, TimeUnit.MINUTES, // idle Threads terminated after one
// minute, when min Pool size exceeded
new ArrayBlockingQueue<Runnable>(10)); // outstanding Runnables are kept here
pool.execute(new Runnable() {
@Override public void run() {
//code to run
}
});
Hinweis Wenn Sie den ThreadPoolExecutor
mit einer unbegrenzten Warteschlange konfigurieren, überschreitet die Thread-Anzahl nicht corePoolSize
da neue Threads nur erstellt werden, wenn die Warteschlange voll ist:
ThreadPoolExecutor mit allen Parametern:
ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime,
TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory,
RejectedExecutionHandler handler)
von JavaDoc
Wenn mehr als corePoolSize, aber weniger als maximumPoolSize-Threads ausgeführt werden, wird ein neuer Thread nur erstellt, wenn die Warteschlange voll ist.
Vorteile:
Die Größe der BlockingQueue kann gesteuert werden, und Szenarien mit zu wenig Arbeitsspeicher können vermieden werden. Die Anwendungsleistung wird durch die begrenzte beschränkte Warteschlangengröße nicht beeinträchtigt.
Sie können vorhandene Richtlinien verwenden oder neue Richtlinien für Ablehnungshandler erstellen.
In der Standardeinstellung ThreadPoolExecutor.AbortPolicy löst der Handler bei Ablehnung eine Laufzeit-RejectedExecutionException aus.
In
ThreadPoolExecutor.CallerRunsPolicy
führt der Thread, der die Ausführung ausführt, die Aufgabe aus. Dies stellt einen einfachen Mechanismus zur Regelung der Rückkopplung bereit, der die Geschwindigkeit der Übergabe neuer Aufgaben verlangsamt.In
ThreadPoolExecutor.DiscardPolicy
wird eine Aufgabe, die nicht ausgeführt werden kann, einfach gelöscht.Wenn der Executor in
ThreadPoolExecutor.DiscardOldestPolicy
nicht heruntergefahren ist, wird die Task am Anfang der Arbeitswarteschlange gelöscht und die Ausführung wird erneut versucht (dies kann erneut fehlschlagen und führt dazu, dass dies wiederholt wird.)
Benutzerdefinierte
ThreadFactory
kann konfiguriert werden, was nützlich ist:- So legen Sie einen aussagekräftigeren Threadnamen fest
- So legen Sie den Thread-Daemon-Status fest
- Thread-Priorität einstellen
Hier ist ein Beispiel für die Verwendung von ThreadPoolExecutor
Wert aus der Berechnung abrufen - Aufrufbar
Wenn Ihre Berechnung einen Rückgabewert liefert, der später erforderlich ist, reicht eine einfache ausführbare Aufgabe nicht aus. In solchen Fällen können Sie ExecutorService.submit(
Callable
<T>)
das nach Abschluss der Ausführung einen Wert zurückgibt.
Der Service gibt eine Future
der Sie das Ergebnis der Taskausführung abrufen können.
// Submit a callable for execution
ExecutorService pool = anExecutorService;
Future<Integer> future = pool.submit(new Callable<Integer>() {
@Override public Integer call() {
//do some computation
return new Random().nextInt();
}
});
// ... perform other tasks while future is executed in a different thread
Wenn Sie das Ergebnis der Zukunft benötigen, rufen Sie future.get()
Warten Sie unbegrenzt auf die Zukunft, um mit einem Ergebnis fertig zu werden.
try { // Blocks current thread until future is completed Integer result = future.get(); catch (InterruptedException || ExecutionException e) { // handle appropriately }
Warten Sie, bis die Zukunft abgeschlossen ist, jedoch nicht länger als die angegebene Zeit.
try { // Blocks current thread for a maximum of 500 milliseconds. // If the future finishes before that, result is returned, // otherwise TimeoutException is thrown. Integer result = future.get(500, TimeUnit.MILLISECONDS); catch (InterruptedException || ExecutionException || TimeoutException e) { // handle appropriately }
Wenn das Ergebnis einer geplanten oder laufenden Aufgabe nicht mehr erforderlich ist, können Sie Future.cancel(boolean)
aufrufen, um den Future.cancel(boolean)
abzubrechen.
- Durch Aufrufen von
cancel(false)
wird die Aufgabe nur aus der Warteschlange der auszuführenden Aufgaben entfernt. - Wenn Sie
cancel(true)
aufrufencancel(true)
wird die Task auch unterbrochen, wenn sie gerade ausgeführt wird.
Planen von Aufgaben zur Ausführung zu einer festgelegten Zeit, nach einer Verzögerung oder wiederholt
Die ScheduledExecutorService
Klasse stellt Methoden zum Planen einzelner oder wiederholter Aufgaben auf verschiedene Arten bereit. Im folgenden Codebeispiel wird davon ausgegangen, dass der pool
wie folgt deklariert und initialisiert wurde:
ScheduledExecutorService pool = Executors.newScheduledThreadPool(2);
Zusätzlich zu den normalen ExecutorService
Methoden fügt die ScheduledExecutorService
API vier Methoden hinzu, die Aufgaben planen und ScheduledFuture
Objekte zurückgeben. Letzteres kann zum Abrufen von Ergebnissen (in einigen Fällen) und zum Abbrechen von Aufgaben verwendet werden.
Start einer Aufgabe nach einer festen Verzögerung
Im folgenden Beispiel wird der Start einer Aufgabe nach zehn Minuten geplant.
ScheduledFuture<Integer> future = pool.schedule(new Callable<>() {
@Override public Integer call() {
// do something
return 42;
}
},
10, TimeUnit.MINUTES);
Aufgaben zu einem festen Preis beginnen
Im folgenden Beispiel wird eine Task so geplant, dass sie nach zehn Minuten startet und dann alle 1 Minute wiederholt wird.
ScheduledFuture<?> future = pool.scheduleAtFixedRate(new Runnable() {
@Override public void run() {
// do something
}
},
10, 1, TimeUnit.MINUTES);
Die Task-Ausführung wird gemäß dem Zeitplan fortgesetzt, bis der pool
heruntergefahren wird, die future
abgebrochen wird oder eine der Aufgaben auf eine Ausnahme stößt.
Es ist garantiert, dass sich die von einem bestimmten scheduledAtFixedRate
Aufruf scheduledAtFixedRate
Tasks nicht zeitlich überlappen. Wenn eine Task länger als die vorgeschriebene Zeit dauert, können die nächste und die nachfolgenden Taskausführungen zu spät beginnen.
Aufgaben werden mit einer festen Verzögerung gestartet
Im folgenden Beispiel wird eine Task so geplant, dass sie nach zehn Minuten startet und dann wiederholt mit einer Verzögerung von einer Minute zwischen einer Taskende und der nächsten Task startet.
ScheduledFuture<?> future = pool.scheduleWithFixedDelay(new Runnable() {
@Override public void run() {
// do something
}
},
10, 1, TimeUnit.MINUTES);
Die Task-Ausführung wird gemäß dem Zeitplan fortgesetzt, bis der pool
heruntergefahren wird, die future
abgebrochen wird oder eine der Aufgaben auf eine Ausnahme stößt.
Abgelehnte Ausführung von Handle
Ob
- Sie versuchen, Aufgaben an einen heruntergefahrenen Executor zu übergeben oder
- Die Warteschlange ist gesättigt (nur bei beschränkten möglich) und die maximale Anzahl Threads wurde erreicht.
RejectedExecutionHandler.rejectedExecution(Runnable, ThreadPoolExecutor)
wird aufgerufen.
Das Standardverhalten ist, dass beim Aufrufer eine RejectedExecutionException ausgelöst wird. Es sind jedoch weitere vordefinierte Verhaltensweisen verfügbar:
- ThreadPoolExecutor.AbortPolicy (standardmäßig REE werfen)
- ThreadPoolExecutor.CallerRunsPolicy (führt eine Task im Thread des Aufrufers aus - blockiert ihn )
- ThreadPoolExecutor.DiscardPolicy (Task im Hintergrund verwerfen)
- ThreadPoolExecutor.DiscardOldestPolicy ( älteste Aufgabe in der Warteschlange automatisch verwerfen und Ausführung der neuen Aufgabe erneut versuchen )
Sie können sie mit einem der ThreadPool- Konstruktoren festlegen:
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
RejectedExecutionHandler handler) // <--
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) // <--
Sie können auch Ihr eigenes Verhalten implementieren, indem Sie die RejectedExecutionHandler- Schnittstelle erweitern:
void rejectedExecution(Runnable r, ThreadPoolExecutor executor)
Unterschiede in der Übergabe von Ausnahmen () im Vergleich zur Ausführung ()
Der Befehl execute () wird im Allgemeinen für Feuer- und Vergissaufrufe verwendet (ohne dass das Ergebnis analysiert werden muss), und der Befehl submit () wird zum Analysieren des Ergebnisses des Future-Objekts verwendet.
Wir sollten uns der Hauptunterschiede der Ausnahmebehandlungsmechanismen zwischen diesen beiden Befehlen bewusst sein.
Ausnahmen von submit () werden vom Framework verschluckt, wenn Sie sie nicht abgefangen haben.
Codebeispiel, um den Unterschied zu verstehen:
Fall 1: Übergeben Sie den Befehl Runnable with execute (), der die Ausnahme meldet.
import java.util.concurrent.*;
import java.util.*;
public class ExecuteSubmitDemo {
public ExecuteSubmitDemo() {
System.out.println("creating service");
ExecutorService service = Executors.newFixedThreadPool(2);
//ExtendedExecutor service = new ExtendedExecutor();
for (int i = 0; i < 2; i++){
service.execute(new Runnable(){
public void run(){
int a = 4, b = 0;
System.out.println("a and b=" + a + ":" + b);
System.out.println("a/b:" + (a / b));
System.out.println("Thread Name in Runnable after divide by zero:"+Thread.currentThread().getName());
}
});
}
service.shutdown();
}
public static void main(String args[]){
ExecuteSubmitDemo demo = new ExecuteSubmitDemo();
}
}
class ExtendedExecutor extends ThreadPoolExecutor {
public ExtendedExecutor() {
super(1, 1, 60, TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(100));
}
// ...
protected void afterExecute(Runnable r, Throwable t) {
super.afterExecute(r, t);
if (t == null && r instanceof Future<?>) {
try {
Object result = ((Future<?>) r).get();
} catch (CancellationException ce) {
t = ce;
} catch (ExecutionException ee) {
t = ee.getCause();
} catch (InterruptedException ie) {
Thread.currentThread().interrupt(); // ignore/reset
}
}
if (t != null)
System.out.println(t);
}
}
Ausgabe:
creating service
a and b=4:0
a and b=4:0
Exception in thread "pool-1-thread-1" Exception in thread "pool-1-thread-2" java.lang.ArithmeticException: / by zero
at ExecuteSubmitDemo$1.run(ExecuteSubmitDemo.java:15)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)
at java.lang.Thread.run(Thread.java:744)
java.lang.ArithmeticException: / by zero
at ExecuteSubmitDemo$1.run(ExecuteSubmitDemo.java:15)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)
at java.lang.Thread.run(Thread.java:744)
Fall 2: Ersetzen von service.submit(new Runnable(){
(): service.submit(new Runnable(){
In diesem Fall werden Ausnahmen vom Framework verschluckt, da die run () - Methode sie nicht explizit service.submit(new Runnable(){
.
Ausgabe:
creating service
a and b=4:0
a and b=4:0
Fall 3: Ändern Sie den neuenFixedThreadPool in ExtendedExecutor
//ExecutorService service = Executors.newFixedThreadPool(2);
ExtendedExecutor service = new ExtendedExecutor();
Ausgabe:
creating service
a and b=4:0
java.lang.ArithmeticException: / by zero
a and b=4:0
java.lang.ArithmeticException: / by zero
Ich habe dieses Beispiel für zwei Themen gezeigt: Verwenden Sie Ihren benutzerdefinierten ThreadPoolExecutor und behandeln Sie Exectpion mit einem benutzerdefinierten ThreadPoolExecutor.
Eine andere einfache Lösung für das obige Problem: Wenn Sie den normalen ExecutorService- & Submit-Befehl verwenden, rufen Sie das Future-Objekt über den submit () - Befehlsaufruf get () für Future ab. Erfassen Sie die drei Ausnahmen, die in der Implementierung der afterExecute-Methode angegeben wurden. Vorteil des benutzerdefinierten ThreadPoolExecutor gegenüber diesem Ansatz: Sie müssen den Ausnahmebehandlungsmechanismus nur an einer Stelle behandeln - Custom ThreadPoolExecutor.
Anwendungsfälle für verschiedene Arten von Parallelitätskonstrukten
ExecutorService executor = Executors.newFixedThreadPool(50);
Es ist einfach und leicht zu bedienen. Es verdeckt
ThreadPoolExecutor
Details vonThreadPoolExecutor
.Ich ziehe dieses vor, wenn die Anzahl der
Callable/Runnable
Aufgaben gering ist und die Anzahl der Aufgaben in der unbegrenzten Warteschlange den Speicher nicht erhöht und die Leistung des Systems nicht beeinträchtigt. Wenn Sie überCPU/Memory
verfügen, ziehe ich es vor,ThreadPoolExecutor
mit Kapazitätsbeschränkungen undRejectedExecutionHandler
zu verwenden, um die Ablehnung von Aufgaben auszuführen.CountDownLatch
wird mit einer bestimmten Anzahl initialisiert. Diese Anzahl wird durch Aufrufe dercountDown()
-MethodecountDown()
. Threads, die darauf warten, dass diese Anzahl Null erreicht, können eine derawait()
Methoden aufrufen. Der Aufruf vonawait()
blockiert den Thread, bis der Zähler null erreicht. Diese Klasse ermöglicht es einem Java-Thread, zu warten, bis eine andere Gruppe von Threads ihre Aufgaben abgeschlossen hat.Anwendungsfälle:
Maximale Parallelität erreichen: Manchmal möchten wir mehrere Threads gleichzeitig starten, um maximale Parallelität zu erreichen
Warten Sie, bis N-Threads abgeschlossen sind, bevor Sie die Ausführung starten
Deadlock-Erkennung
ThreadPoolExecutor : Es bietet mehr Kontrolle. Wenn die Anwendung durch die Anzahl der ausstehenden ausgeführten / aufrufbaren Aufgaben eingeschränkt wird, können Sie die beschränkte Warteschlange verwenden, indem Sie die maximale Kapazität festlegen. Sobald die Warteschlange die maximale Kapazität erreicht hat, können Sie RejectionHandler definieren. Java bietet vier Arten von
RejectedExecutionHandler
Richtlinien .ThreadPoolExecutor.AbortPolicy
, der HandlerThreadPoolExecutor.AbortPolicy
bei Ablehnung eine Laufzeit-RejectedExecutionException aus.ThreadPoolExecutor.CallerRunsPolicy`, der Thread, der die Ausführung ausführt, führt die Aufgabe aus. Dies stellt einen einfachen Mechanismus zur Regelung der Rückkopplung bereit, der die Geschwindigkeit der Übergabe neuer Aufgaben verlangsamt.
In
ThreadPoolExecutor.DiscardPolicy
wird eine Aufgabe, die nicht ausgeführt werden kann, einfach gelöscht.ThreadPoolExecutor.DiscardOldestPolicy
: Wenn der Executor nicht heruntergefahren wird, wird die Task am Anfang der Arbeitswarteschlange gelöscht und die Ausführung wird erneut versucht (dies kann erneut fehlschlagen und führt dazu, dass dies wiederholt wird.)
Wenn Sie das CountDownLatch
Verhalten simulieren möchten, können Sie die invokeAll()
Methode verwenden.
Ein weiterer Mechanismus, den Sie nicht zitiert haben, ist ForkJoinPool
Der
ForkJoinPool
wurde Java in Java 7 hinzugefügt. DerForkJoinPool
ähnelt dem JavaExecutorService
jedoch mit einem Unterschied. Mit demForkJoinPool
können Aufgaben einfach in kleinere Aufgaben aufgeteilt werden, die dann ebenfalls an denForkJoinPool
werden. Das Stehlen von Aufgaben geschieht inForkJoinPool
wenn freie Worker-Threads Aufgaben aus der beschäftigten Worker-Warteschlange stehlen.Java 8 hat in ExecutorService eine weitere API zum Erstellen von Workstealing-Pools eingeführt. Sie müssen
RecursiveTask
undRecursiveAction
nicht erstellen, können jedoch weiterhinForkJoinPool
.public static ExecutorService newWorkStealingPool()
Erstellt einen Worksteal-Thread-Pool, der alle verfügbaren Prozessoren als Ziel-Parallelitätsebene verwendet.
Standardmäßig wird die Anzahl der CPU-Kerne als Parameter verwendet.
Alle diese vier Mechanismen ergänzen einander. Abhängig von der Granularität, die Sie steuern möchten, müssen Sie die richtige auswählen.
Warten Sie auf den Abschluss aller Aufgaben in ExecutorService
Schauen wir uns die verschiedenen Optionen an, um zu warten, bis die an Executor gesendeten Aufgaben abgeschlossen sind
- ExecutorService
invokeAll()
Führt die angegebenen Aufgaben aus und gibt eine Liste der Futures zurück, die ihren Status und die Ergebnisse enthalten, wenn alles abgeschlossen ist.
Beispiel:
import java.util.concurrent.*;
import java.util.*;
public class InvokeAllDemo{
public InvokeAllDemo(){
System.out.println("creating service");
ExecutorService service = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
List<MyCallable> futureList = new ArrayList<MyCallable>();
for (int i = 0; i < 10; i++){
MyCallable myCallable = new MyCallable((long)i);
futureList.add(myCallable);
}
System.out.println("Start");
try{
List<Future<Long>> futures = service.invokeAll(futureList);
} catch(Exception err){
err.printStackTrace();
}
System.out.println("Completed");
service.shutdown();
}
public static void main(String args[]){
InvokeAllDemo demo = new InvokeAllDemo();
}
class MyCallable implements Callable<Long>{
Long id = 0L;
public MyCallable(Long val){
this.id = val;
}
public Long call(){
// Add your business logic
return id;
}
}
}
Eine Synchronisationshilfe, mit der ein oder mehrere Threads warten können, bis ein Satz von Vorgängen in anderen Threads abgeschlossen ist.
Ein CountDownLatch wird mit einer bestimmten Anzahl initialisiert. Die wait-Methoden werden blockiert, bis der aktuelle Zählerstand null ist, weil
countDown()
dercountDown()
-MethodecountDown()
, wonach alle wartenden Threads freigegeben werden und alle nachfolgenden Aufrufe von await sofort zurückgegeben werden. Dies ist ein einmaliges Phänomen - die Zählung kann nicht zurückgesetzt werden. Wenn Sie eine Version benötigen, die die Zählung zurücksetzt, sollten Sie einen CyclicBarrier verwenden .ForkJoinPool oder
newWorkStealingPool()
in ExecutorsDurchlaufen Sie alle
Future
Objekte, die nach dem Senden anExecutorService
Empfohlene Methode zum Herunterfahren von der Oracle-Dokumentationsseite von ExecutorService :
void shutdownAndAwaitTermination(ExecutorService pool) { pool.shutdown(); // Disable new tasks from being submitted try { // Wait a while for existing tasks to terminate if (!pool.awaitTermination(60, TimeUnit.SECONDS)) { pool.shutdownNow(); // Cancel currently executing tasks // Wait a while for tasks to respond to being cancelled if (!pool.awaitTermination(60, TimeUnit.SECONDS)) System.err.println("Pool did not terminate"); } } catch (InterruptedException ie) { // (Re-)Cancel if current thread also interrupted pool.shutdownNow(); // Preserve interrupt status Thread.currentThread().interrupt(); }
shutdown():
Startet ein ordnungsgemäßes Herunterfahren, bei dem zuvor gesendete Aufgaben ausgeführt werden, jedoch keine neuen Aufgaben angenommen werden.shutdownNow():
Versucht, alle aktiv ausgeführten Aufgaben zu stoppen, hält die Verarbeitung wartender Aufgaben an und gibt eine Liste der Aufgaben zurück, die auf ihre Ausführung warteten.Wenn im obigen Beispiel Ihre Aufgaben mehr Zeit in Anspruch nehmen, können Sie den if-Zustand in den while-Zustand ändern
Ersetzen
if (!pool.awaitTermination(60, TimeUnit.SECONDS))
mit
while(!pool.awaitTermination(60, TimeUnit.SECONDS)) { Thread.sleep(60000);
}
Anwendungsfälle für verschiedene Typen von ExecutorService
Executors gibt verschiedene Arten von ThreadPools zurück, die auf die jeweiligen Bedürfnisse zugeschnitten sind.
public static ExecutorService newSingleThreadExecutor()
Erstellt einen Executor, der einen einzelnen Worker-Thread verwendet, der in einer unbegrenzten Warteschlange ausgeführt wird
Es gibt einen Unterschied zwischen
newFixedThreadPool(1)
undnewSingleThreadExecutor()
wie das Java-Dokument für Letzteres sagt:Im Gegensatz zum ansonsten äquivalenten newFixedThreadPool (1) ist der zurückgegebene Executor garantiert nicht für die Verwendung zusätzlicher Threads rekonfigurierbar.
newFixedThreadPool
bedeutet, dass einnewFixedThreadPool
später im Programm neunewFixedThreadPool
kann durch:((ThreadPoolExecutor) fixedThreadPool).setMaximumPoolSize(10)
Dies ist fürnewSingleThreadExecutor
nicht möglichAnwendungsfälle:
- Sie möchten die übergebenen Aufgaben in einer Reihenfolge ausführen.
- Sie benötigen nur einen Thread, um alle Ihre Anfragen zu bearbeiten
Nachteile:
- Ungebundene Warteschlangen sind schädlich
public static ExecutorService newFixedThreadPool(int nThreads)
Erstellt einen Thread-Pool, der eine feste Anzahl von Threads wiederverwendet, die in einer gemeinsam genutzten, nicht gebundenen Warteschlange ausgeführt werden. Zu jedem Zeitpunkt sind höchstens nThreads-Threads aktive Verarbeitungsaufgaben. Wenn zusätzliche Tasks übergeben werden, während alle Threads aktiv sind, warten sie in der Warteschlange, bis ein Thread verfügbar ist
Anwendungsfälle:
- Effektive Nutzung der verfügbaren Kerne. Konfigurieren Sie
nThreads
alsRuntime.getRuntime().availableProcessors()
- Wenn Sie entscheiden, dass die Anzahl der Threads eine Anzahl im Thread-Pool nicht überschreiten soll
Nachteile:
- Ungebundene Warteschlangen sind schädlich.
- Effektive Nutzung der verfügbaren Kerne. Konfigurieren Sie
public static ExecutorService newCachedThreadPool()
Erstellt einen Thread-Pool, der nach Bedarf neue Threads erstellt, zuvor erstellte Threads jedoch wieder verwendet, wenn sie verfügbar sind
Anwendungsfälle:
- Für kurzlebige asynchrone Aufgaben
Nachteile:
- Ungebundene Warteschlangen sind schädlich.
- Jede neue Aufgabe erstellt einen neuen Thread, wenn alle vorhandenen Threads belegt sind. Wenn die Task längere Zeit in Anspruch nimmt, werden mehr Threads erstellt, was die Leistung des Systems beeinträchtigt. Alternative in diesem Fall:
newFixedThreadPool
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize)
Erstellt einen Thread-Pool, der Befehle planen kann, die nach einer bestimmten Verzögerung ausgeführt werden oder regelmäßig ausgeführt werden.
Anwendungsfälle:
- Behandlung wiederkehrender Ereignisse mit Verzögerungen, die in bestimmten Zeitabständen in der Zukunft auftreten werden
Nachteile:
- Ungebundene Warteschlangen sind schädlich.
5.
public static ExecutorService newWorkStealingPool()
Erstellt einen Worksteal-Thread-Pool, der alle verfügbaren Prozessoren als Ziel-Parallelitätsebene verwendet
Anwendungsfälle:
- Für das Teilen und Erobern von Problemen.
- Effektive Verwendung von im Leerlauf befindlichen Threads. Leere Threads stehlen Aufgaben aus ausgelasteten Threads.
Nachteile:
- Nicht gebundene Warteschlangengröße ist schädlich.
In all diesen ExecutorService können Sie einen Nachteil feststellen: die unbegrenzte Warteschlange. Dies wird mit ThreadPoolExecutor angesprochen
ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime,
TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory,
RejectedExecutionHandler handler)
Mit ThreadPoolExecutor
können Sie
- Kontrollieren Sie die Thread-Poolgröße dynamisch
- Stellen Sie die Kapazität für
BlockingQueue
- Definieren Sie
RejectionExecutionHander
wenn die Warteschlange voll ist -
CustomThreadFactory
, um einige zusätzliche Funktionen während der Thread-Erstellung hinzuzufügen(public Thread newThread(Runnable r)
Thread-Pools verwenden
Thread-Pools werden hauptsächlich in ExecutorService
.
Die folgenden Methoden können verwendet werden, um Arbeit zur Ausführung einzureichen:
Methode | Beschreibung |
---|---|
submit | Führt die eingereichte Arbeit aus und gibt eine Zukunft zurück, die zum Abrufen des Ergebnisses verwendet werden kann |
execute | Führen Sie die Aufgabe irgendwann in der Zukunft aus, ohne einen Rückgabewert zu erhalten |
invokeAll | Führen Sie eine Liste mit Aufgaben aus und geben Sie eine Liste mit Futures zurück |
invokeAny | Führt alle aus, gibt jedoch nur das Ergebnis eines erfolgreichen Ergebnisses zurück (ohne Ausnahmen) |
Wenn Sie mit dem Thread-Pool fertig sind, können Sie shutdown()
aufrufen, um den Thread-Pool zu beenden. Dadurch werden alle anstehenden Aufgaben ausgeführt. Um auf die Ausführung aller Tasks zu warten, können Sie awaitTermination
oder isShutdown()
.