FixedThreadPool vs CachedThreadPool: das kleinere von zwei Übeln

93

Ich habe ein Programm, das Threads (~ 5-150) erzeugt, die eine Reihe von Aufgaben ausführen. Ursprünglich habe ich a verwendet, FixedThreadPoolweil diese ähnliche Frage darauf hindeutete, dass sie besser für längerlebige Aufgaben geeignet sind, und mit meinen sehr begrenzten Kenntnissen über Multithreading habe ich die durchschnittliche Lebensdauer der Threads (mehrere Minuten) als " langlebig " angesehen.

Ich habe jedoch kürzlich die Möglichkeit hinzugefügt, zusätzliche Threads zu erzeugen, wodurch ich über das von mir festgelegte Thread-Limit hinausgehe. Wäre es in diesem Fall besser, die Anzahl der Threads zu erraten und zu erhöhen, die ich zulassen kann, oder zu einem zu wechseln, CachedThreadPooldamit ich keine verschwendeten Threads habe?

Wenn ich sie beide vorab ausprobiere, scheint es keinen Unterschied zu geben, also neige ich dazu, mich CachedThreadPoolnur für die Verschwendung zu entscheiden. Bedeutet die Lebensdauer der Threads jedoch, dass ich stattdessen eine auswählen FixedThreadPoolund mich nur mit den nicht verwendeten Threads befassen sollte? Diese Frage lässt den Eindruck entstehen, dass diese zusätzlichen Themen nicht verschwendet werden, aber ich würde mich über die Klarstellung freuen.

Daniel
quelle

Antworten:

108

Ein CachedThreadPool ist genau das, was Sie für Ihre Situation verwenden sollten, da die Verwendung eines CachedThreadPool für lange laufende Threads keine negativen Konsequenzen hat. Der Kommentar im Java-Dokument, dass CachedThreadPools für kurze Aufgaben geeignet ist, deutet lediglich darauf hin, dass sie für solche Fälle besonders geeignet sind und nicht für Aufgaben mit langen Aufgaben verwendet werden können oder sollten.

Auszuarbeiten weiter, Executors.newCachedThreadPool und Executors.newFixedThreadPool gesichert beide vom selben Thread - Pool - Implementierung (zumindest im offenen JDK) nur mit unterschiedlichen Parametern. Die Unterschiede sind nur das Minimum, Maximum, die Thread-Kill-Zeit und der Warteschlangentyp.

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>());
}

Ein FixedThreadPool hat seine Vorteile, wenn Sie tatsächlich mit einer festen Anzahl von Threads arbeiten möchten, da Sie dann eine beliebige Anzahl von Aufgaben an den Executor-Service senden können, ohne zu wissen, dass die Anzahl der Threads auf der von Ihnen angegebenen Ebene beibehalten wird. Wenn Sie die Anzahl der Threads explizit erhöhen möchten, ist dies nicht die richtige Wahl.

Dies bedeutet jedoch, dass das einzige Problem, das Sie möglicherweise mit dem CachedThreadPool haben, darin besteht, die Anzahl der Threads zu begrenzen, die gleichzeitig ausgeführt werden. Der CachedThreadPool schränkt sie nicht für Sie ein. Daher müssen Sie möglicherweise Ihren eigenen Code schreiben, um sicherzustellen, dass Sie nicht zu viele Threads ausführen. Dies hängt wirklich vom Design Ihrer Anwendung ab und davon, wie Aufgaben an den Executor-Service gesendet werden.

Trevor Freeman
quelle
1
"Ein CachedThreadPool ist genau das, was Sie für Ihre Situation verwenden sollten, da die Verwendung eines CachedThreadPool für lange laufende Threads keine negativen Konsequenzen hat." Ich glaube nicht, dass ich damit einverstanden bin. CachedThreadPool erstellt dynamisch Threads ohne Obergrenze. Lang laufende Aufgaben auf einer großen Anzahl von Threads können möglicherweise alle Ressourcen belasten. Wenn mehr Threads als ideal vorhanden sind, können zu viele Ressourcen beim Kontextwechsel dieser Threads verschwendet werden. Obwohl Sie am Ende der Antwort erklärt haben, dass eine benutzerdefinierte Drosselung erforderlich ist, ist der Anfang der Antwort etwas irreführend.
Nishit
1
Warum nicht einfach ein begrenztes ThreadPoolExecutorLike erstellen ThreadPoolExecutor(0, maximumPoolSize, 60L, TimeUnit.SECONDS, SynchronousQueue())?
Abhijit Sarkar
45

Beides FixedThreadPoolund CachedThreadPoolsind Übel in hoch belasteten Anwendungen.

CachedThreadPool ist gefährlicher als FixedThreadPool

Wenn Ihre Anwendung stark ausgelastet ist und eine geringe Latenz erfordert, sollten Sie beide Optionen aufgrund der folgenden Nachteile besser entfernen

  1. Unbegrenzte Art der Aufgabenwarteschlange: Dies kann zu Speichermangel oder hoher Latenz führen
  2. Lang laufende Threads führen dazu CachedThreadPool, dass die Thread-Erstellung außer Kontrolle gerät

Da Sie wissen, dass beide Übel sind, tut weniger Böses nichts Gutes. Bevorzugen Sie ThreadPoolExecutor , der eine detaillierte Steuerung vieler Parameter bietet.

  1. Legen Sie die Aufgabenwarteschlange als begrenzte Warteschlange fest, um eine bessere Kontrolle zu erhalten
  2. Haben Sie den richtigen RejectionHandler - Ihren eigenen RejectionHandler oder Standardhandler, der von JDK bereitgestellt wird
  3. Wenn Sie vor / nach Abschluss der Aufgabe etwas zu tun haben, überschreiben Sie beforeExecute(Thread, Runnable)undafterExecute(Runnable, Throwable)
  4. Außer Kraft setzen Thread wenn Thread Anpassung erforderlich ist
  5. Steuern Sie die Größe des Thread-Pools dynamisch zur Laufzeit (zugehörige SE-Frage: Dynamic Thread Pool )
Ravindra Babu
quelle
Was ist, wenn sich jemand für commonPool entscheidet?
Crosk Cool
1
@ Ravindra - Sie haben die Nachteile von CachedThreadPool und FixedThreadPool sehr gut erklärt. Dies zeigt, dass Sie ein tiefes Verständnis für das Parallelitätspaket haben.
Ayaskant
5

Ich habe also ein Programm, das Threads (~ 5-150) erzeugt, die eine Reihe von Aufgaben ausführen.

Sind Sie sicher, dass Sie verstehen, wie Threads tatsächlich von Ihrem Betriebssystem und der Hardware Ihrer Wahl verarbeitet werden? Wie ordnet Java Threads OS-Threads zu, wie ordnet das Threads CPU-Threads usw. zu? Ich frage, weil das Erstellen von 150 Threads in ONE JRE nur dann sinnvoll ist, wenn Sie massive CPU-Kerne / Threads darunter haben, was höchstwahrscheinlich nicht der Fall ist. Je nach verwendetem Betriebssystem und RAM kann das Erstellen von mehr als n Threads sogar dazu führen, dass Ihre JRE aufgrund von OOM-Fehlern beendet wird. Sie sollten also wirklich zwischen Threads und der von diesen Threads auszuführenden Arbeit unterscheiden, wie viele Arbeiten Sie überhaupt verarbeiten können usw.

Und das ist das Problem mit CachedThreadPool: Es ist nicht sinnvoll, lange laufende Arbeiten in Threads in die Warteschlange zu stellen, die tatsächlich nicht ausgeführt werden können, da nur 2 CPU-Kerne diese Threads verarbeiten können. Wenn Sie am Ende 150 geplante Threads haben, können Sie viel unnötigen Overhead für die Scheduler verursachen, die in Java und im Betriebssystem verwendet werden, um sie gleichzeitig zu verarbeiten. Dies ist einfach unmöglich, wenn Sie nur 2 CPU-Kerne haben, es sei denn, Ihre Threads warten die ganze Zeit auf E / A oder ähnliches. Aber selbst in diesem Fall würden viele Threads eine Menge E / A erzeugen ...

Und dieses Problem tritt bei FixedThreadPool nicht auf, das mit z. B. 2 + n Threads erstellt wurde, wobei n natürlich vernünftig niedrig ist, da damit Hardware- und Betriebssystemressourcen mit weitaus weniger Aufwand für die Verwaltung von Threads verwendet werden, die ohnehin nicht ausgeführt werden können.

Thorsten Schöning
quelle
Manchmal gibt es keine bessere Wahl, Sie könnten nur einen CPU-Kern haben, aber wenn Sie einen Server ausführen, auf dem jede Benutzeranforderung einen Thread zur Verarbeitung der Anforderung auslösen würde, gibt es keine andere vernünftige Auswahl, insbesondere wenn Sie planen um den Server zu skalieren, sobald Sie Ihre Benutzerbasis vergrößert haben.
Michel Feinstein
@mFeinstein Wie kann man keine Wahl haben, wenn man in der Lage ist, eine Thread-Pool-Implementierung zu wählen? In Ihrem Beispiel mit 1 CPU-Kern macht es einfach keinen Sinn, mehr Threads zu erzeugen. Es passt perfekt zu meinem Beispiel mit einem FixedThreadPool. Das lässt sich auch leicht skalieren, zuerst mit einem oder zwei Worker-Threads, später mit 10 oder 15, abhängig von der Anzahl der Kerne.
Thorsten Schöning
2
Die überwiegende Mehrheit der Webserver-Implementierungen erstellt für jede neue HTTP-Anforderung einen neuen Thread. Es ist ihnen egal, wie viele tatsächliche Kerne der Computer hat. Dies macht die Implementierung einfacher und einfacher zu skalieren. Dies gilt für viele andere Designs, bei denen Sie nur einmal codieren und bereitstellen möchten und nicht neu kompilieren und erneut bereitstellen müssen, wenn Sie den Computer ändern, bei dem es sich möglicherweise um eine Cloud-Instanz handelt.
Michel Feinstein
@mFeinstein Die meisten Webserver verwenden Thread-Pools für eigene Anforderungen, einfach weil das Laichen von Threads, die nicht ausgeführt werden können, keinen Sinn ergibt, oder sie verwenden Ereignisschleifen für Verbindungen und verarbeiten die Anforderungen anschließend in Pools oder dergleichen. Außerdem fehlt Ihnen der Punkt, nämlich dass die Frage, ob man den richtigen Thread-Pool auswählen kann und das Laichen von Threads, die sowieso nicht ausgeführt werden können, immer noch keinen Sinn ergibt. Ein FixedthreadPool, der auf eine angemessene Anzahl von Threads pro Computer konfiguriert ist, hängt von den Kernen ab.
Thorsten Schöning
3
@ ThorstenSchöning, 50 CPU-gebundene Threads auf einem 2-Core-Rechner zu haben, ist nicht hilfreich. Es kann sehr hilfreich sein, 50 IO-gebundene Threads auf einem 2-Core-Computer zu haben.
Paul Draper