newCachedThreadPool()
gegen newFixedThreadPool()
Wann sollte ich das eine oder andere verwenden? Welche Strategie ist in Bezug auf die Ressourcennutzung besser?
newCachedThreadPool()
gegen newFixedThreadPool()
Wann sollte ich das eine oder andere verwenden? Welche Strategie ist in Bezug auf die Ressourcennutzung besser?
Antworten:
Ich denke, die Dokumente erklären den Unterschied und die Verwendung dieser beiden Funktionen ziemlich gut:
Erstellt einen Thread-Pool, der eine feste Anzahl von Threads wiederverwendet, die in einer gemeinsam genutzten unbegrenzten Warteschlange ausgeführt werden. Zu jedem Zeitpunkt sind höchstens nThreads-Threads aktive Verarbeitungsaufgaben. Wenn zusätzliche Aufgaben gesendet werden, während alle Threads aktiv sind, warten sie in der Warteschlange, bis ein Thread verfügbar ist. Wenn ein Thread aufgrund eines Fehlers während der Ausführung vor dem Herunterfahren beendet wird, wird bei Bedarf ein neuer Thread ersetzt, um nachfolgende Aufgaben auszuführen. Die Threads im Pool bleiben bestehen, bis sie explizit heruntergefahren werden.
Erstellt einen Thread-Pool, der nach Bedarf neue Threads erstellt, zuvor erstellte Threads jedoch wiederverwendet, sobald sie verfügbar sind. Diese Pools verbessern normalerweise die Leistung von Programmen, die viele kurzlebige asynchrone Aufgaben ausführen. Durch auszuführende Aufrufe werden zuvor erstellte Threads wiederverwendet, sofern verfügbar. Wenn kein vorhandener Thread verfügbar ist, wird ein neuer Thread erstellt und dem Pool hinzugefügt. Threads, die seit 60 Sekunden nicht mehr verwendet wurden, werden beendet und aus dem Cache entfernt. Ein Pool, der lange genug inaktiv bleibt, verbraucht daher keine Ressourcen. Beachten Sie, dass Pools mit ähnlichen Eigenschaften, aber unterschiedlichen Details (z. B. Timeout-Parametern) mithilfe von ThreadPoolExecutor-Konstruktoren erstellt werden können.
In Bezug auf die Ressourcen newFixedThreadPool
werden alle Threads so lange ausgeführt, bis sie explizit beendet werden. In den newCachedThreadPool
Threads, die seit 60 Sekunden nicht mehr verwendet wurden, werden sie beendet und aus dem Cache entfernt.
Vor diesem Hintergrund wird der Ressourcenverbrauch sehr stark von der Situation abhängen. Zum Beispiel, wenn Sie eine große Anzahl von Aufgaben mit langer Laufzeit haben, würde ich die vorschlagen FixedThreadPool
. In CachedThreadPool
den Dokumenten heißt es: "Diese Pools verbessern normalerweise die Leistung von Programmen, die viele kurzlebige asynchrone Aufgaben ausführen."
newCachedThreadPool
dass dies einige schwerwiegende Probleme verursachen kann, da Sie die gesamte Kontrolle dem thread pool
und überlassen, wenn der Dienst mit anderen auf demselben Host arbeitet , was dazu führen kann, dass die anderen aufgrund des langen Wartens der CPU abstürzen. Ich denke newFixedThreadPool
also, dass dies in einem solchen Szenario sicherer sein kann. Auch dieser Beitrag verdeutlicht die herausragendsten Unterschiede zwischen ihnen.
Um die anderen Antworten zu vervollständigen, möchte ich Effective Java, 2. Auflage, von Joshua Bloch, Kapitel 10, Punkt 68, zitieren:
"Die Auswahl des Executor-Dienstes für eine bestimmte Anwendung kann schwierig sein. Wenn Sie ein kleines Programm oder einen leicht ausgelasteten Server schreiben, ist die Verwendung von Executors.new-CachedThreadPool im Allgemeinen eine gute Wahl , da keine Konfiguration erforderlich ist und im Allgemeinen" das Richtige." Ein zwischengespeicherter Thread-Pool ist jedoch keine gute Wahl für einen stark ausgelasteten Produktionsserver !
In einem zwischengespeicherten Thread-Pool werden übermittelte Aufgaben nicht in die Warteschlange gestellt, sondern sofort zur Ausführung an einen Thread übergeben. Wenn keine Threads verfügbar sind, wird ein neuer erstellt . Wenn ein Server so stark ausgelastet ist, dass alle CPUs voll ausgelastet sind und mehr Aufgaben eintreffen, werden mehr Threads erstellt, was die Sache nur noch schlimmer macht.
Daher ist es auf einem stark ausgelasteten Produktionsserver viel besser, Executors.newFixedThreadPool zu verwenden , das Ihnen einen Pool mit einer festen Anzahl von Threads bietet, oder die ThreadPoolExecutor-Klasse direkt zu verwenden, um maximale Kontrolle zu erhalten. ""
Wenn Sie sich den Quellcode ansehen , werden Sie sehen, dass sie ThreadPoolExecutor aufrufen . intern und Einstellung ihrer Eigenschaften. Sie können Ihre erstellen, um Ihre Anforderungen besser kontrollieren zu können.
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
Wenn Sie sich keine Sorgen über eine unbegrenzte Warteschlange an aufrufbaren / ausführbaren Aufgaben machen, können Sie eine davon verwenden. Wie von bruno vorgeschlagen, ziehe ich es auch newFixedThreadPool
zu newCachedThreadPool
mehr als diese beiden.
Aber ThreadPoolExecutor bietet flexiblere Funktionen im Vergleich zu entweder newFixedThreadPool
odernewCachedThreadPool
ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime,
TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory,
RejectedExecutionHandler handler)
Vorteile:
Sie haben die volle Kontrolle über die BlockingQueue- Größe. Im Gegensatz zu den beiden vorherigen Optionen ist es nicht unbegrenzt. Ich bekomme keinen Speicherfehler aufgrund einer großen Anzahl anstehender Callable / Runnable-Aufgaben, wenn unerwartete Turbulenzen im System auftreten.
Sie können eine benutzerdefinierte Ablehnungsbehandlungsrichtlinie implementieren ODER eine der folgenden Richtlinien verwenden:
Standardmäßig ThreadPoolExecutor.AbortPolicy
löst der Handler bei Ablehnung eine Laufzeit-RejectedExecutionException aus.
In ThreadPoolExecutor.CallerRunsPolicy
führt der Thread, der die Ausführung selbst aufruft, die Aufgabe aus. Dies bietet einen einfachen Mechanismus zur Steuerung der Rückmeldung, der die Übermittlungsrate neuer Aufgaben verlangsamt.
In ThreadPoolExecutor.DiscardPolicy
wird eine Aufgabe, die nicht ausgeführt werden kann, einfach gelöscht.
In ThreadPoolExecutor.DiscardOldestPolicy
, wenn der Vollstrecker nicht heruntergefahren wird, wird die Aufgabe an der Spitze der Warteschlange gelöscht, und dann wird die Ausführung erneut versucht (die wiederum fehlschlagen kann, so dass diese wiederholt werden.)
Sie können eine benutzerdefinierte Thread-Factory für die folgenden Anwendungsfälle implementieren:
Das ist richtig, Executors.newCachedThreadPool()
keine gute Wahl für Servercode, der mehrere Clients und gleichzeitige Anforderungen bedient.
Warum? Grundsätzlich gibt es zwei (verwandte) Probleme:
Es ist unbegrenzt, was bedeutet, dass Sie jedem die Tür öffnen, um Ihre JVM zu lähmen, indem Sie einfach mehr Arbeit in den Dienst einbringen (DoS-Angriff). Threads verbrauchen eine nicht zu vernachlässigende Menge an Speicher und erhöhen auch den Speicherverbrauch basierend auf ihren laufenden Arbeiten. Daher ist es recht einfach, einen Server auf diese Weise zu stürzen (es sei denn, Sie haben andere Leistungsschalter installiert).
Das unbegrenzte Problem wird durch die Tatsache verschärft, dass der Executor mit einem konfrontiert wird, SynchronousQueue
was bedeutet, dass eine direkte Übergabe zwischen dem Task-Geber und dem Thread-Pool besteht. Jede neue Aufgabe erstellt einen neuen Thread, wenn alle vorhandenen Threads ausgelastet sind. Dies ist im Allgemeinen eine schlechte Strategie für Servercode. Wenn die CPU voll ist, dauert es länger, bis vorhandene Aufgaben abgeschlossen sind. Es werden jedoch immer mehr Aufgaben eingereicht und mehr Threads erstellt, sodass die Ausführung von Aufgaben immer länger dauert. Wenn die CPU gesättigt ist, sind mehr Threads definitiv nicht das, was der Server benötigt.
Hier sind meine Empfehlungen:
Verwenden Sie einen Thread-Pool mit fester Größe Executors.newFixedThreadPool oder einen ThreadPoolExecutor. mit einer festgelegten maximalen Anzahl von Threads;
Die ThreadPoolExecutor
Klasse ist die Basisimplementierung für die Executoren, die von vielen Executors
Factory-Methoden zurückgegeben werden. Also lassen Sie uns nähern Feste und Cached Thread - Pools von ThreadPoolExecutor
‚s Perspektive.
Der Hauptkonstruktor dieser Klasse sieht folgendermaßen aus:
public ThreadPoolExecutor(
int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler
)
Das corePoolSize
bestimmt die Mindestgröße des Ziel-Thread-Pools. Die Implementierung würde einen Pool dieser Größe beibehalten, selbst wenn keine auszuführenden Aufgaben vorhanden sind.
Dies maximumPoolSize
ist die maximale Anzahl von Threads, die gleichzeitig aktiv sein können.
Nachdem der Thread-Pool gewachsen ist und den corePoolSize
Schwellenwert überschritten hat, kann der Executor inaktive Threads beenden und corePoolSize
erneut auf den Thread zugreifen . Wenn dies der allowCoreThreadTimeOut
Fall ist, kann der Executor sogar Core-Pool-Threads beenden, wenn sie mehr als den keepAliveTime
Schwellenwert im Leerlauf waren .
Wenn Threads also länger als der keepAliveTime
Schwellenwert im Leerlauf bleiben , werden sie möglicherweise beendet, da keine Nachfrage nach ihnen besteht.
Was passiert, wenn eine neue Aufgabe eingeht und alle Kernthreads belegt sind? Die neuen Aufgaben werden in dieser BlockingQueue<Runnable>
Instanz in die Warteschlange gestellt . Wenn ein Thread frei wird, kann eine dieser Aufgaben in der Warteschlange verarbeitet werden.
Es gibt verschiedene Implementierungen der BlockingQueue
Schnittstelle in Java, so dass wir verschiedene Warteschlangenansätze implementieren können, wie:
Begrenzte Warteschlange : Neue Aufgaben werden in einer begrenzten Aufgabenwarteschlange in die Warteschlange gestellt.
Ungebundene Warteschlange : Neue Aufgaben werden in einer unbegrenzten Aufgabenwarteschlange in die Warteschlange gestellt. Diese Warteschlange kann also so stark wachsen, wie es die Heap-Größe zulässt.
Synchrone Übergabe : Mit können Sie auch SynchronousQueue
die neuen Aufgaben in die Warteschlange stellen. In diesem Fall muss beim Einreihen einer neuen Aufgabe bereits ein anderer Thread auf diese Aufgabe warten.
So ThreadPoolExecutor
führt der eine neue Aufgabe aus:
corePoolSize
Threads ausgeführt werden, wird versucht, einen neuen Thread mit der angegebenen Aufgabe als erstem Job zu starten.BlockingQueue#offer
Methode in die Warteschlange zu stellen
. Die offer
Methode wird nicht blockiert, wenn die Warteschlange voll ist, und kehrt sofort zurück false
.offer
zurückgegeben wird false
), wird versucht, dem Thread-Pool einen neuen Thread mit dieser Aufgabe als erstem Job hinzuzufügen.RejectedExecutionHandler
.Der Hauptunterschied zwischen den festen und zwischengespeicherten Thread-Pools beruht auf diesen drei Faktoren:
+ ----------- + ----------- + ------------------- + ----- ---------------------------- + | Pooltyp | Kerngröße | Maximale Größe | Warteschlangenstrategie | + ----------- + ----------- + ------------------- + ----- ---------------------------- + | Behoben | n (fest) | n (fest) | Ungebundene `LinkedBlockingQueue` | + ----------- + ----------- + ------------------- + ----- ---------------------------- + | Zwischengespeichert | 0 | Integer.MAX_VALUE | `SynchronousQueue` | + ----------- + ----------- + ------------------- + ----- ---------------------------- +
Excutors.newFixedThreadPool(n)
:
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
Wie du siehst:
OutOfMemoryError
.Wann sollte ich das eine oder andere verwenden? Welche Strategie ist in Bezug auf die Ressourcennutzung besser?
Ein Thread-Pool mit fester Größe scheint ein guter Kandidat zu sein, wenn wir die Anzahl gleichzeitiger Aufgaben für Ressourcenverwaltungszwecke begrenzen möchten .
Wenn wir beispielsweise einen Executor verwenden, um Webserveranforderungen zu verarbeiten, kann ein fester Executor die Anforderungsbursts angemessener verarbeiten.
Für ein noch besseres Ressourcenmanagement wird dringend empfohlen, eine benutzerdefinierte Version ThreadPoolExecutor
mit einer begrenzten BlockingQueue<T>
Implementierung und einer angemessenen Implementierung zu erstellen RejectedExecutionHandler
.
So funktioniert das Executors.newCachedThreadPool()
:
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
Wie du siehst:
Integer.MAX_VALUE
. Praktisch ist der Thread-Pool unbegrenzt.SynchronousQueue
immer fehlschlägt, wenn am anderen Ende niemand ist, der sie akzeptiert!Wann sollte ich das eine oder andere verwenden? Welche Strategie ist in Bezug auf die Ressourcennutzung besser?
Verwenden Sie es, wenn Sie viele vorhersehbare kurzfristige Aufgaben haben.
Sie dürfen newCachedThreadPool nur verwenden, wenn Sie kurzlebige asynchrone Aufgaben haben, wie in Javadoc angegeben. Wenn Sie Aufgaben senden, deren Verarbeitung länger dauert, werden am Ende zu viele Threads erstellt. Sie können 100% der CPU erreichen, wenn Sie Aufgaben mit langer Laufzeit schneller an newCachedThreadPool senden ( http://rashcoder.com/be-careful-while-using-executors-newcachedthreadpool/ ).
Ich mache einige Schnelltests und habe folgende Ergebnisse:
1) bei Verwendung von SynchronousQueue:
Nachdem die Threads die maximale Größe erreicht haben, werden alle neuen Arbeiten mit der folgenden Ausnahme abgelehnt.
Ausnahme im Thread "main" java.util.concurrent.RejectedExecutionException: Task java.util.concurrent.FutureTask@3fee733d abgelehnt von java.util.concurrent.ThreadPoolExecutor@5acf9800 [Laufende, Poolgröße = 3, aktive Threads = 3, Aufgaben in der Warteschlange = 0, erledigte Aufgaben = 0]
at java.util.concurrent.ThreadPoolExecutor $ AbortPolicy.rejectedExecution (ThreadPoolExecutor.java:2047)
2) bei Verwendung von LinkedBlockingQueue:
Die Threads werden niemals von minimaler Größe auf maximale Größe erhöht, was bedeutet, dass der Thread-Pool eine feste Größe als minimale Größe hat.