Executors.newCachedThreadPool () versus Executors.newFixedThreadPool ()

Antworten:

202

Ich denke, die Dokumente erklären den Unterschied und die Verwendung dieser beiden Funktionen ziemlich gut:

newFixedThreadPool

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.

newCachedThreadPool

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 newFixedThreadPoolwerden alle Threads so lange ausgeführt, bis sie explizit beendet werden. In den newCachedThreadPoolThreads, 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 CachedThreadPoolden Dokumenten heißt es: "Diese Pools verbessern normalerweise die Leistung von Programmen, die viele kurzlebige asynchrone Aufgaben ausführen."

bruno conde
quelle
1
Ja, ich habe die Dokumente durchgesehen. Das Problem ist ... FixedThreadPool verursacht einen Speicherfehler bei 3 Threads. Da CachedPool intern nur einen einzigen Thread erstellt. Beim Erhöhen der Heap-Größe erhalte ich das gleiche Leistung für beide .. gibt es noch etwas, was ich vermisse !!
Hakish
1
Stellen Sie eine Threadfactory für den ThreadPool bereit? Meine Vermutung ist, dass möglicherweise ein Status in den Threads gespeichert wird, der nicht durch Müll gesammelt wird. Wenn nicht, wird Ihr Programm möglicherweise so nahe an der Größe des Heap-Limits ausgeführt, dass beim Erstellen von 3 Threads ein OutOfMemory verursacht wird. Wenn cachedPool intern nur einen einzelnen Thread erstellt, weist dies möglicherweise darauf hin, dass Ihre Aufgaben synchronisiert ausgeführt werden.
Bruno Conde
@brunoconde Genau wie @Louis F. darauf hinweist, newCachedThreadPooldass dies einige schwerwiegende Probleme verursachen kann, da Sie die gesamte Kontrolle dem thread poolund ü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 newFixedThreadPoolalso, dass dies in einem solchen Szenario sicherer sein kann. Auch dieser Beitrag verdeutlicht die herausragendsten Unterschiede zwischen ihnen.
Gehört
75

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. ""

Louis F.
quelle
15

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>());
}
krmanish007
quelle
1
Genau, ein zwischengespeicherter Thread-Executor mit einer vernünftigen Obergrenze und beispielsweise 5-10 Minuten Leerlaufernte ist für die meisten Gelegenheiten einfach perfekt.
Agoston Horvath
12

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 newFixedThreadPoolzu newCachedThreadPoolmehr als diese beiden.

Aber ThreadPoolExecutor bietet flexiblere Funktionen im Vergleich zu entweder newFixedThreadPoolodernewCachedThreadPool

ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, 
TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory,
RejectedExecutionHandler handler)

Vorteile:

  1. 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.

  2. Sie können eine benutzerdefinierte Ablehnungsbehandlungsrichtlinie implementieren ODER eine der folgenden Richtlinien verwenden:

    1. Standardmäßig ThreadPoolExecutor.AbortPolicylöst der Handler bei Ablehnung eine Laufzeit-RejectedExecutionException aus.

    2. In ThreadPoolExecutor.CallerRunsPolicyfü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.

    3. In ThreadPoolExecutor.DiscardPolicywird eine Aufgabe, die nicht ausgeführt werden kann, einfach gelöscht.

    4. 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.)

  3. Sie können eine benutzerdefinierte Thread-Factory für die folgenden Anwendungsfälle implementieren:

    1. So legen Sie einen aussagekräftigeren Threadnamen fest
    2. So legen Sie den Thread-Daemon-Status fest
    3. Thread-Priorität festlegen
Ravindra Babu
quelle
11

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:

  1. 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).

  2. Das unbegrenzte Problem wird durch die Tatsache verschärft, dass der Executor mit einem konfrontiert wird, SynchronousQueuewas 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;

Prashant Gautam
quelle
6

Die ThreadPoolExecutorKlasse ist die Basisimplementierung für die Executoren, die von vielen ExecutorsFactory-Methoden zurückgegeben werden. Also lassen Sie uns nähern Feste und Cached Thread - Pools von ThreadPoolExecutor‚s Perspektive.

ThreadPoolExecutor

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
)

Kernpoolgröße

Das corePoolSizebestimmt 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.

Maximale Poolgröße

Dies maximumPoolSizeist die maximale Anzahl von Threads, die gleichzeitig aktiv sein können.

Nachdem der Thread-Pool gewachsen ist und den corePoolSizeSchwellenwert überschritten hat, kann der Executor inaktive Threads beenden und corePoolSizeerneut auf den Thread zugreifen . Wenn dies der allowCoreThreadTimeOutFall ist, kann der Executor sogar Core-Pool-Threads beenden, wenn sie mehr als den keepAliveTimeSchwellenwert im Leerlauf waren .

Wenn Threads also länger als der keepAliveTimeSchwellenwert im Leerlauf bleiben , werden sie möglicherweise beendet, da keine Nachfrage nach ihnen besteht.

Anstehen

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 BlockingQueueSchnittstelle in Java, so dass wir verschiedene Warteschlangenansätze implementieren können, wie:

  1. Begrenzte Warteschlange : Neue Aufgaben werden in einer begrenzten Aufgabenwarteschlange in die Warteschlange gestellt.

  2. 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.

  3. Synchrone Übergabe : Mit können Sie auch SynchronousQueuedie neuen Aufgaben in die Warteschlange stellen. In diesem Fall muss beim Einreihen einer neuen Aufgabe bereits ein anderer Thread auf diese Aufgabe warten.

Arbeitseinreichung

So ThreadPoolExecutorführt der eine neue Aufgabe aus:

  1. Wenn weniger als corePoolSizeThreads ausgeführt werden, wird versucht, einen neuen Thread mit der angegebenen Aufgabe als erstem Job zu starten.
  2. Andernfalls wird versucht, die neue Aufgabe mithilfe der BlockingQueue#offerMethode in die Warteschlange zu stellen . Die offerMethode wird nicht blockiert, wenn die Warteschlange voll ist, und kehrt sofort zurück false.
  3. Wenn die neue Aufgabe nicht in die Warteschlange gestellt werden kann (dh offerzurückgegeben wird false), wird versucht, dem Thread-Pool einen neuen Thread mit dieser Aufgabe als erstem Job hinzuzufügen.
  4. Wenn der neue Thread nicht hinzugefügt werden kann, wird der Executor entweder heruntergefahren oder ist gesättigt. In jedem Fall würde die neue Aufgabe mit der bereitgestellten Aufgabe abgelehnt RejectedExecutionHandler.

Der Hauptunterschied zwischen den festen und zwischengespeicherten Thread-Pools beruht auf diesen drei Faktoren:

  1. Kernpoolgröße
  2. Maximale Poolgröße
  3. Anstehen
+ ----------- + ----------- + ------------------- + ----- ---------------------------- +
| Pooltyp | Kerngröße | Maximale Größe | Warteschlangenstrategie |
+ ----------- + ----------- + ------------------- + ----- ---------------------------- +
| Behoben | n (fest) | n (fest) | Ungebundene `LinkedBlockingQueue` |
+ ----------- + ----------- + ------------------- + ----- ---------------------------- +
| Zwischengespeichert | 0 | Integer.MAX_VALUE | `SynchronousQueue` |
+ ----------- + ----------- + ------------------- + ----- ---------------------------- +


Fadenpool behoben


So funktioniert das Excutors.newFixedThreadPool(n):

public static ExecutorService newFixedThreadPool(int nThreads) {
    return new ThreadPoolExecutor(nThreads, nThreads,
                                  0L, TimeUnit.MILLISECONDS,
                                  new LinkedBlockingQueue<Runnable>());
}

Wie du siehst:

  • Die Größe des Thread-Pools ist festgelegt.
  • Wenn es eine hohe Nachfrage gibt, wird sie nicht wachsen.
  • Wenn Threads längere Zeit nicht verwendet werden, schrumpft sie nicht.
  • Angenommen, all diese Threads sind mit einigen lang laufenden Aufgaben beschäftigt und die Ankunftsrate ist immer noch ziemlich hoch. Da der Executor eine unbegrenzte Warteschlange verwendet, kann er einen großen Teil des Heaps verbrauchen. Wenn wir unglücklich genug sind, können wir eine erleben 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 ThreadPoolExecutormit einer begrenzten BlockingQueue<T>Implementierung und einer angemessenen Implementierung zu erstellen RejectedExecutionHandler.


Zwischengespeicherter Thread-Pool


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:

  • Der Thread-Pool kann von null Threads auf wachsen Integer.MAX_VALUE. Praktisch ist der Thread-Pool unbegrenzt.
  • Wenn ein Thread länger als 1 Minute inaktiv ist, wird er möglicherweise beendet. Der Pool kann also schrumpfen, wenn die Threads zu viel im Leerlauf bleiben.
  • Wenn alle zugewiesenen Threads belegt sind, während eine neue Aufgabe eingeht, wird ein neuer Thread erstellt, da das Anbieten einer neuen Aufgabe für einen SynchronousQueueimmer 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.

Ali Dehghani
quelle
5

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/ ).

Dhanaraj Durairaj
quelle
1

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.

Mike Lin
quelle