Un altro tipo di pool di thread è "memorizzato nella cache". Tali pool di thread sono usati altrettanto comunemente di quelli fissi.

Come indicato dal nome, questo tipo di pool di thread memorizza nella cache i thread. Mantiene attivi i thread inutilizzati per un periodo di tempo limitato al fine di riutilizzarli per eseguire nuove attività. Un tale pool di thread è il migliore per quando abbiamo una quantità ragionevole di lavoro leggero.

Il significato di "una quantità ragionevole" è piuttosto ampio, ma dovresti sapere che un tale pool non è adatto a tutti i compiti. Ad esempio, supponiamo di voler creare un milione di attività. Anche se ognuno richiede una quantità di tempo molto ridotta, utilizzeremo comunque una quantità irragionevole di risorse e degraderemo le prestazioni. Dovremmo anche evitare tali pool quando il tempo di esecuzione è imprevedibile, ad esempio con le attività di I/O.

Sotto il cofano, il costruttore ThreadPoolExecutor viene chiamato con i seguenti argomenti:

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

I seguenti valori vengono passati al costruttore come argomenti:

Parametro Valore
corePoolSize (quanti thread saranno pronti (avviati) all'avvio del servizio executor ) 0
maximumPoolSize (il numero massimo di thread che un servizio esecutore può creare) Numero intero.MAX_VALUE
keepAliveTime (il tempo in cui un thread liberato continuerà a vivere prima di essere distrutto se il numero di thread è maggiore di corePoolSize ) 60L
unità (unità di tempo) Unità di tempo.SECONDI
workQueue (implementazione di una coda) new SynchronousQueue<Eseguibile>()

E possiamo passare la nostra implementazione di ThreadFactory esattamente allo stesso modo.

Parliamo di SynchronousQueue

L'idea di base di un trasferimento sincrono è piuttosto semplice e tuttavia controintuitiva (ovvero, l'intuizione o il buon senso ti dice che è sbagliato): puoi aggiungere un elemento a una coda se e solo se un altro thread riceve l' elemento al contemporaneamente. In altre parole, una coda sincrona non può contenere attività, perché non appena arriva una nuova attività, il thread in esecuzione ha già prelevato l'attività .

Quando una nuova attività entra nella coda, se c'è un thread attivo libero nel pool, riprende l'attività. Se tutti i thread sono occupati, viene creato un nuovo thread.

Un pool memorizzato nella cache inizia con zero thread e può potenzialmente crescere fino a thread Integer.MAX_VALUE . In sostanza, la dimensione di un pool di thread memorizzato nella cache è limitata solo dalle risorse di sistema.

Per conservare le risorse di sistema, i pool di thread memorizzati nella cache rimuovono i thread inattivi per un minuto.

Vediamo come funziona in pratica. Creeremo una classe di attività che modella una richiesta utente:

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

Nel metodo principale , creiamo newCachedThreadPool e quindi aggiungiamo 3 attività per l'esecuzione. Qui stampiamo lo stato del nostro servizio (1) .

Successivamente, ci fermiamo per 30 secondi, avviamo un'altra attività e visualizziamo lo stato (2) .

Successivamente, mettiamo in pausa il nostro thread principale per 70 secondi, stampiamo lo stato (3) , quindi aggiungiamo nuovamente 3 attività e stampiamo nuovamente lo stato (4) .

Nei punti in cui mostriamo lo stato subito dopo l'aggiunta di un'attività, aggiungiamo prima una sospensione di 1 secondo per l'output aggiornato.

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

Ed ecco il risultato:

Richiesta utente elaborata n. 0 sul thread pool-1-thread-1
Richiesta utente elaborata n. 1 sul thread pool-1-thread-2
Richiesta utente elaborata n. 2 sul thread pool-1-thread-3
(1) java.util.concurrent .ThreadPoolExecutor@f6f4d33[In esecuzione, dimensione pool = 3, thread attivi = 0, attività in coda = 0, attività completate = 3]
Richiesta utente n. 3 elaborata sul thread pool-1-thread-2
(2) java.util.concurrent. ThreadPoolExecutor@f6f4d33[In esecuzione, dimensione pool = 3, thread attivi = 0, attività in coda = 0, attività completate = 4] (3)
java.util.concurrent.ThreadPoolExecutor@f6f4d33[In esecuzione, dimensione pool = 0, thread attivi = 0 , attività in coda = 0, attività completate = 4]
Richiesta utente elaborata n. 4 sul thread pool-1-thread-4
Richiesta utente elaborata n. 5 sul thread pool-1-thread-5
Richiesta utente elaborata n. 6 sul thread pool-1-thread-4
(4) java.util.concurrent.ThreadPoolExecutor@f6f4d33[In esecuzione, dimensione pool = 2, thread attivi = 0, attività in coda = 0, attività completate = 7]

Esaminiamo ciascuno dei passaggi:

Fare un passo Spiegazione
1 (dopo 3 compiti completati) Abbiamo creato 3 thread e 3 attività sono state eseguite su questi tre thread.
Quando viene visualizzato lo stato, tutte e 3 le attività sono state completate ei thread sono pronti per eseguire altre attività.
2 (dopo una pausa di 30 secondi e l'esecuzione di un'altra attività) Dopo 30 secondi di inattività, i thread sono ancora attivi e in attesa di attività.
Un'altra attività viene aggiunta ed eseguita su un thread prelevato dal pool dei rimanenti thread live.
Nessun nuovo thread è stato aggiunto al pool.
3 (dopo una pausa di 70 secondi) I thread sono stati rimossi dal pool.
Non ci sono thread pronti per accettare attività.
4 (dopo aver eseguito altre 3 attività) Dopo che sono state ricevute più attività, sono stati creati nuovi thread. Questa volta solo due thread sono riusciti a elaborare 3 attività.

Bene, ora conosci la logica di un altro tipo di servizio esecutore.

Per analogia con altri metodi della classe di utilità Executors , anche il metodo newCachedThreadPool ha una versione in overload che accetta un oggetto ThreadFactory come argomento.