FixedThreadPool vs CachedThreadPool: il minore di due mali


93

Ho un programma che genera thread (~ 5-150) che eseguono una serie di attività. Inizialmente, ho usato un FixedThreadPoolperché questa domanda simile suggeriva che fossero più adatti per attività più longeve e con la mia conoscenza molto limitata del multithreading, consideravo la vita media dei thread (diversi minuti) "di lunga durata ".

Tuttavia, di recente ho aggiunto la possibilità di generare thread aggiuntivi e così facendo mi porta al di sopra del limite di thread che ho impostato. In questo caso, sarebbe meglio indovinare e aumentare il numero di thread che posso consentire o passare a un fileCachedThreadPool modo da non avere thread sprecati?

Provandoli entrambi preliminarmente, non sembra esserci differenza, quindi sono propenso ad andare con il CachedThreadPoolsolo per evitare gli sprechi. Tuttavia, la durata dei thread significa che dovrei invece scegliere FixedThreadPoole occuparmi solo dei thread inutilizzati? Questa domanda fa sembrare che quei thread extra non siano sprecati, ma apprezzerei il chiarimento.

Risposte:


108

Un CachedThreadPool è esattamente ciò che dovresti usare per la tua situazione in quanto non ci sono conseguenze negative nell'usarne uno per thread a lunga esecuzione. Il commento nel documento java sul fatto che CachedThreadPools sia adatto per attività brevi suggerisce semplicemente che sono particolarmente appropriati per tali casi, non che non possono o non dovrebbero essere usati per attività che implicano attività a lunga esecuzione.

Per approfondire ulteriormente, Executors.newCachedThreadPool ed Executors.newFixedThreadPool sono entrambi supportati dalla stessa implementazione del pool di thread (almeno nel JDK aperto) solo con parametri diversi. Le differenze sono solo il loro thread minimo, massimo, tempo di kill del thread e tipo di coda.

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

Un FixedThreadPool ha i suoi vantaggi quando in realtà vuoi lavorare con un numero fisso di thread, da allora puoi inviare un numero qualsiasi di attività al servizio esecutore sapendo che il numero di thread verrà mantenuto al livello specificato. Se si desidera esplicitamente aumentare il numero di thread, questa non è la scelta appropriata.

Ciò tuttavia significa che l'unico problema che potresti avere con CachedThreadPool riguarda la limitazione del numero di thread in esecuzione contemporaneamente. Il CachedThreadPool non li limiterà per te, quindi potresti dover scrivere il tuo codice per assicurarti di non eseguire troppi thread. Questo dipende molto dal design della tua applicazione e da come le attività vengono inviate al servizio di esecutore.


1
"Un CachedThreadPool è esattamente ciò che dovresti usare per la tua situazione in quanto non ci sono conseguenze negative nell'usarne uno per thread a lunga esecuzione". Non credo di essere d'accordo. CachedThreadPool crea dinamicamente thread senza limite superiore. Le attività a lunga esecuzione su un numero elevato di thread possono potenzialmente consumare tutte le risorse. Inoltre, avere più thread dell'ideale può causare troppe risorse sprecate per il cambio di contesto di questi thread. Sebbene tu abbia spiegato alla fine della risposta che è richiesto il throttling personalizzato, l'inizio della risposta è un po 'fuorviante.
Nishit

1
Perché non creare semplicemente un ThreadPoolExecutortipo limitato ThreadPoolExecutor(0, maximumPoolSize, 60L, TimeUnit.SECONDS, SynchronousQueue())?
Abhijit Sarkar

45

Entrambi FixedThreadPoole CachedThreadPoolsono mali in applicazioni altamente caricate.

CachedThreadPool è più pericoloso di FixedThreadPool

Se la tua applicazione è molto caricata e richiede una bassa latenza, è meglio eliminare entrambe le opzioni a causa dei seguenti inconvenienti

  1. Natura illimitata della coda delle attività: potrebbe causare memoria insufficiente o latenza elevata
  2. I thread a esecuzione prolungata faranno CachedThreadPoolandare fuori controllo durante la creazione del thread

Poiché sai che entrambi sono mali, il male minore non fa bene. Preferire ThreadPoolExecutor , che fornisce un controllo granulare su molti parametri.

  1. Imposta la coda delle attività come coda delimitata per avere un controllo migliore
  2. Avere il diritto RejectionHandler: i propri gestori RejectionHandler o Default forniti da JDK
  3. Se hai qualcosa da fare prima / dopo il completamento dell'attività, sovrascrivi beforeExecute(Thread, Runnable)eafterExecute(Runnable, Throwable)
  4. Eseguire l' override di ThreadFactory se è richiesta la personalizzazione del thread
  5. Controllare le dimensioni del pool di thread dinamicamente in fase di esecuzione (domanda SE correlata: pool di thread dinamico )

E se qualcuno decide di utilizzare commonPool?
Crosk Cool

1
@Ravindra - Hai spiegato magnificamente i contro di CachedThreadPool e FixedThreadPool. Questo dimostra che hai una profonda conoscenza del pacchetto di concorrenza.
Ayaskant

5

Quindi ho un programma che genera thread (~ 5-150) che eseguono una serie di attività.

Sei sicuro di aver capito come i thread vengono effettivamente elaborati dal tuo sistema operativo e hardware preferito? In che modo Java associa i thread ai thread del sistema operativo, come associa i thread ai thread della CPU, ecc.? Sto chiedendo perché la creazione di 150 thread all'interno di ONE JRE ha senso solo se si hanno enormi core / thread della CPU sotto, il che molto probabilmente non è il caso. A seconda del sistema operativo e della RAM in uso, la creazione di più di n thread potrebbe persino causare la chiusura di JRE a causa di errori OOM. Quindi dovresti davvero distinguere tra thread e lavoro da fare da quei thread, quanti lavori sei persino in grado di elaborare ecc.

E questo è il problema con CachedThreadPool: non ha senso mettere in coda il lavoro a lunga esecuzione in thread che in realtà non possono essere eseguiti perché hai solo 2 core CPU in grado di elaborare quei thread. Se ti ritrovi con 150 thread pianificati, potresti creare un sovraccarico non necessario per gli scheduler utilizzati in Java e il sistema operativo per elaborarli contemporaneamente. Questo è semplicemente impossibile se hai solo 2 core della CPU, a meno che i tuoi thread non stiano aspettando I / O o cose del genere tutto il tempo. Ma anche in quel caso molti thread creerebbero molto I / O ...

E quel problema non si verifica con FixedThreadPool, creato ad esempio con 2 + n thread, dove n è ovviamente abbastanza basso, perché con quell'hardware e le risorse del sistema operativo vengono utilizzate con molto meno overhead per la gestione di thread che non possono essere eseguiti comunque.


A volte non c'è una scelta migliore, potresti avere solo 1 core della CPU ma se stai eseguendo un server in cui ogni richiesta dell'utente attiverebbe un thread per elaborare la richiesta, non ci sarà altra scelta ragionevole, specialmente se pianifichi per scalare il server una volta cresciuta la tua base di utenti.
Michel Feinstein

@mFeinstein Come si può non avere scelta se si è nella posizione di scegliere un'implementazione del pool di thread? Nel tuo esempio con 1 core della CPU, generare solo più thread semplicemente non ha alcun senso, si adatta perfettamente al mio esempio utilizzando un FixedThreadPool. Anche questo si ridimensiona facilmente, prima con uno o due thread di lavoro, successivamente con 10 o 15 a seconda del numero di core.
Thorsten Schöning

2
La stragrande maggioranza delle implementazioni dei server web creerà un nuovo thread per ogni nuova richiesta HTTP ... Non si preoccuperanno di quanti core effettivi ha la macchina, questo rende l'implementazione più semplice e più facile da scalare. Ciò si applica a molti altri progetti in cui si desidera solo codificare una volta e distribuire, senza dover ricompilare e ridistribuire se si modifica la macchina, che potrebbe essere un'istanza cloud.
Michel Feinstein

@mFeinstein La maggior parte dei server Web utilizza i pool di thread per le richieste da soli, semplicemente perché la generazione di thread che non possono essere eseguiti non ha senso, oppure utilizzano loop di eventi per le connessioni ed elaborano le richieste nei pool in seguito o simili. Inoltre, ti manca il punto, ovvero che la domanda riguarda la possibilità di scegliere il pool di thread corretto e la generazione di thread che non possono essere eseguiti comunque non ha senso. Un FixedthreadPool configurato per una quantità ragionevole di thread per macchina a seconda dei core si ridimensiona perfettamente.
Thorsten Schöning,

3
@ ThorstenSchöning, avere 50 thread associati alla CPU su una macchina a 2 core non è di aiuto. Avere 50 thread associati a IO su una macchina a 2 core può essere molto utile.
Paul Draper
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.