Eine andere Art von Thread-Pool ist „cached“. Solche Thread-Pools werden ebenso häufig verwendet wie feste.

Wie der Name schon sagt, werden bei dieser Art von Thread-Pool Threads zwischengespeichert. Es hält ungenutzte Threads für eine begrenzte Zeit am Leben, um diese Threads für die Ausführung neuer Aufgaben wiederzuverwenden. Ein solcher Thread-Pool eignet sich am besten, wenn wir einigermaßen wenig Arbeit haben.

Die Bedeutung von „etwas angemessener Menge“ ist ziemlich weit gefasst, aber Sie sollten wissen, dass ein solcher Pool nicht für jede Anzahl von Aufgaben geeignet ist. Angenommen, wir möchten eine Million Aufgaben erstellen. Auch wenn jeder Vorgang nur sehr wenig Zeit in Anspruch nimmt, werden wir dennoch unverhältnismäßig viele Ressourcen verbrauchen und die Leistung beeinträchtigen. Wir sollten solche Pools auch vermeiden, wenn die Ausführungszeit unvorhersehbar ist, beispielsweise bei I/O-Aufgaben.

Unter der Haube wird der ThreadPoolExecutor- Konstruktor mit den folgenden Argumenten aufgerufen:

public static ExecutorService newCachedThreadPool() {
    return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS,
      new SynchronousQueue<Runnable>());
}

Als Argumente werden dem Konstruktor folgende Werte übergeben:

Parameter Wert
corePoolSize (wie viele Threads bereit (gestartet) sein, wenn der Executor- Dienst startet) 0
MaximumPoolSize (die maximale Anzahl von Threads, die ein Executor- Dienst erstellen kann) Ganzzahl.MAX_VALUE
keepAliveTime (die Zeit, die ein freigegebener Thread weiterlebt, bevor er zerstört wird, wenn die Anzahl der Threads größer als corePoolSize ist ) 60L
Einheit (Zeiteinheiten) TimeUnit.SECONDS
workQueue (Implementierung einer Warteschlange) new SynchronousQueue<Runnable>()

Und wir können unsere eigene Implementierung von ThreadFactory auf genau die gleiche Weise übergeben.

Lassen Sie uns über SynchronousQueue sprechen

Die Grundidee einer synchronen Übertragung ist recht einfach und dennoch kontraintuitiv (das heißt, die Intuition oder der gesunde Menschenverstand sagen Ihnen, dass sie falsch ist): Sie können ein Element genau dann zu einer Warteschlange hinzufügen, wenn ein anderer Thread das Element am erhält gleiche Zeit. Mit anderen Worten: Eine synchrone Warteschlange kann keine Aufgaben enthalten, da der ausführende Thread die Aufgabe bereits übernommen hat, sobald eine neue Aufgabe eintrifft .

Wenn eine neue Aufgabe in die Warteschlange gelangt und ein freier aktiver Thread im Pool vorhanden ist, wird die Aufgabe übernommen. Wenn alle Threads beschäftigt sind, wird ein neuer Thread erstellt.

Ein zwischengespeicherter Pool beginnt mit null Threads und kann möglicherweise auf Integer.MAX_VALUE- Threads anwachsen. Im Wesentlichen wird die Größe eines zwischengespeicherten Thread-Pools nur durch die Systemressourcen begrenzt.

Um Systemressourcen zu schonen, entfernen zwischengespeicherte Thread-Pools Threads, die eine Minute lang inaktiv sind.

Mal sehen, wie es in der Praxis funktioniert. Wir erstellen eine Aufgabenklasse, die eine Benutzeranfrage modelliert:

public class Task implements Runnable {
   int taskNumber;

   public Task(int taskNumber) {
       this.taskNumber = taskNumber;
   }

   @Override
   public void run() {
       System.out.println("Processed user request #" + taskNumber + " on thread " + Thread.currentThread().getName());
   }
}

In der Hauptmethode erstellen wir newCachedThreadPool und fügen dann drei Aufgaben zur Ausführung hinzu. Hier drucken wir den Status unseres Dienstes aus (1) .

Als nächstes pausieren wir 30 Sekunden, starten eine weitere Aufgabe und zeigen den Status (2) an .

Danach pausieren wir unseren Hauptthread für 70 Sekunden, drucken den Status (3) aus, fügen dann erneut 3 Aufgaben hinzu und drucken erneut den Status (4) aus .

An Stellen, an denen wir den Status unmittelbar nach dem Hinzufügen einer Aufgabe anzeigen, fügen wir zunächst einen Ruhezustand von 1 Sekunde ein, um eine aktuelle Ausgabe zu gewährleisten.

ExecutorService executorService = Executors.newCachedThreadPool();
        for (int i = 0; i < 3; i++) {
            executorService.submit(new Task(i));
        }

        TimeUnit.SECONDS.sleep(1);
            System.out.println(executorService);	//(1)

        TimeUnit.SECONDS.sleep(30);

        executorService.submit(new Task(3));
        TimeUnit.SECONDS.sleep(1);
            System.out.println(executorService);	//(2)

        TimeUnit.SECONDS.sleep(70);

            System.out.println(executorService);	//(3)

        for (int i = 4; i < 7; i++) {
            executorService.submit(new Task(i));
        }

        TimeUnit.SECONDS.sleep(1);
            System.out.println(executorService);	//(4)
        executorService.shutdown();

Und hier ist das Ergebnis:

Verarbeitete Benutzeranforderung Nr. 0 im Thread „Pool-1-Thread-1“
Verarbeitete Benutzeranforderung Nr. 1 im Thread „Pool-1-Thread-2“
Verarbeitete Benutzeranforderung Nr. 2 im Thread „Pool-1-Thread-3“
(1) java.util.concurrent .ThreadPoolExecutor@f6f4d33[Wird ausgeführt, Poolgröße = 3, aktive Threads = 0, Aufgaben in der Warteschlange = 0, abgeschlossene Aufgaben = 3]
Verarbeitete Benutzeranforderung Nr. 3 auf Pool-1-Thread-2-Thread
(2) java.util.concurrent. ThreadPoolExecutor@f6f4d33[Wird ausgeführt, Poolgröße = 3, aktive Threads = 0, Aufgaben in der Warteschlange = 0, abgeschlossene Aufgaben = 4] (3) java.util.concurrent.ThreadPoolExecutor@f6f4d33[Wird ausgeführt, Poolgröße = 0
, aktive Threads = 0 , Aufgaben in der Warteschlange = 0, abgeschlossene Aufgaben = 4]
Benutzeranforderung Nr. 4 im Thread „Pool-1-Thread-4“ verarbeitet.
Benutzeranforderung Nr. 5 im Thread „Pool-1-Thread-5“ verarbeitet
Verarbeitete Benutzeranforderung Nr. 6 im Pool-1-Thread-4-Thread
(4) java.util.concurrent.ThreadPoolExecutor@f6f4d33[Wird ausgeführt, Poolgröße = 2, aktive Threads = 0, Aufgaben in der Warteschlange = 0, abgeschlossene Aufgaben = 7]

Gehen wir die einzelnen Schritte durch:

Schritt Erläuterung
1 (nach 3 erledigten Aufgaben) Wir haben 3 Threads erstellt und 3 Aufgaben wurden in diesen drei Threads ausgeführt.
Wenn der Status angezeigt wird, sind alle drei Aufgaben erledigt und die Threads sind bereit, andere Aufgaben auszuführen.
2 (nach 30 Sekunden Pause und Ausführung einer anderen Aufgabe) Nach 30 Sekunden Inaktivität sind die Threads immer noch aktiv und warten auf Aufgaben.
Eine weitere Aufgabe wird hinzugefügt und in einem Thread ausgeführt, der aus dem Pool der verbleibenden Live-Threads entnommen wird.
Dem Pool wurde kein neuer Thread hinzugefügt.
3 (nach einer 70-sekündigen Pause) Die Threads wurden aus dem Pool entfernt.
Es sind keine Threads vorhanden, die bereit sind, Aufgaben anzunehmen.
4 (nach Ausführung von 3 weiteren Aufgaben) Nachdem weitere Aufgaben eingegangen waren, wurden neue Threads erstellt. Dieses Mal haben es nur zwei Threads geschafft, drei Aufgaben zu verarbeiten.

Nun sind Sie mit der Logik einer anderen Art von Executor-Service vertraut.

Analog zu anderen Methoden der Executors -Dienstprogrammklasse gibt es auch für die newCachedThreadPool- Methode eine überladene Version, die ein ThreadFactory- Objekt als Argument verwendet.