Executors.newCachedThreadPool () versus Executors.newFixedThreadPool ()

newCachedThreadPool() versus newFixedThreadPool()

Quando dovrei usare l’uno o l’altro? Quale strategia è migliore in termini di utilizzo delle risorse?

Penso che i documenti spieghino abbastanza bene la differenza e l’uso di queste due funzioni:

newFixedThreadPool

Crea un pool di thread che riutilizza un numero fisso di thread che operano su una coda non associata condivisa. In qualsiasi momento, al massimo i thread nThreads saranno attività di elaborazione attive. Se vengono inoltrate attività aggiuntive quando tutti i thread sono attivi, attenderanno in coda fino a quando un thread non sarà disponibile. Se un thread si interrompe a causa di un errore durante l’esecuzione prima dello spegnimento, ne verrà sostituito uno nuovo se necessario per eseguire le attività successive. I thread nel pool esisteranno fino a quando non verranno arrestati esplicitamente.

newCachedThreadPool

Crea un pool di thread che crea nuovi thread in base alle necessità, ma riutilizzerà i thread creati in precedenza quando sono disponibili. Questi pool in genere migliorano le prestazioni dei programmi che eseguono molte attività asincrone di breve durata. Le chiamate da eseguire riutilizzeranno i thread precedentemente costruiti, se disponibili. Se nessun thread esistente è disponibile, un nuovo thread verrà creato e aggiunto al pool. I thread che non sono stati usati per sessanta secondi sono terminati e rimossi dalla cache. Quindi, un pool che rimane inattivo abbastanza a lungo non consumerà risorse. Si noti che pool con proprietà simili ma dettagli diversi (ad esempio, parametri di timeout) possono essere creati utilizzando i costruttori ThreadPoolExecutor.

In termini di risorse, il nuovo newFixedThreadPool manterrà tutti i thread in esecuzione fino alla loro chiusura esplicita. Nel thread newCachedThreadPool thread che non sono stati utilizzati per sessanta secondi sono terminati e rimossi dalla cache.

Detto questo, il consumo di risorse dipenderà molto dalla situazione. Ad esempio, se si dispone di un numero enorme di attività di lunga durata, suggerirei il FixedThreadPool . Per quanto riguarda CachedThreadPool , i documenti dicono che “Questi pool migliorano tipicamente le prestazioni dei programmi che eseguono molte attività asincrone di breve durata”.

Solo per completare le altre risposte, vorrei citare Effective Java, 2nd Edition, di Joshua Bloch, capitolo 10, articolo 68:

“Scegliere il servizio executor per una particolare applicazione può essere complicato: se stai scrivendo un piccolo programma o un server leggermente carico , usare Executors.new- CachedThreadPool è generalmente una buona scelta , in quanto non richiede alcuna configurazione e generalmente” fa il cosa giusta. “Ma un pool di thread in cache non è una buona scelta per un server di produzione pesantemente caricato !

In un pool di thread memorizzato nella cache , le attività inoltrate non vengono messe in coda ma vengono immediatamente passate a un thread per l’esecuzione. Se nessun thread è disponibile, ne viene creato uno nuovo . Se un server è così pesantemente caricato che tutte le sue CPU sono completamente utilizzate e arrivano più compiti, verranno creati più thread, il che peggiorerà solo le cose.

Pertanto, in un server di produzione pesantemente caricato , è molto meglio utilizzare Executors.newFixedThreadPool , che fornisce un pool con un numero fisso di thread o utilizzando direttamente la class ThreadPoolExecutor per il massimo controllo.

Se vedi il codice in grepcode, vedrai, stanno chiamando ThreadPoolExecutor. internamente e impostando le loro proprietà. Puoi crearne uno per avere un controllo migliore delle tue esigenze.

 public static ExecutorService newFixedThreadPool(int nThreads) { return new ThreadPoolExecutor(nThreads, nThreads,0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue()); } public static ExecutorService newCachedThreadPool() { return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue()); } 

Esatto, Executors.newCachedThreadPool() non è un’ottima scelta per il codice server che serve più client e richieste simultanee.

Perché? Ci sono fondamentalmente due problemi (correlati) con esso:

  1. È illimitato, il che vuol dire che stai aprendo la porta a chiunque di paralizzare la tua JVM semplicemente iniettando più lavoro nel servizio (attacco DoS). I thread consumano una quantità di memoria non trascurabile e aumentano anche il consumo di memoria in base al loro work-in-progress, quindi è abbastanza semplice rovesciare un server in questo modo (a meno che non siano presenti altri interruttori automatici).

  2. Il problema illimitato è esacerbato dal fatto che l’Executor è preceduto da un SynchronousQueue che significa che c’è un handoff diretto tra il task-giver e il pool di thread. Ogni nuova attività creerà un nuovo thread se tutti i thread esistenti sono occupati. Questa è generalmente una ctriggers strategia per il codice del server. Quando la CPU si satura, le attività esistenti impiegano più tempo per terminare. Tuttavia, vengono inviate più attività e vengono creati più thread, pertanto le attività richiedono più tempo e più tempo per essere completate. Quando la CPU è satura, più thread non sono sicuramente ciò di cui il server ha bisogno.

Ecco i miei consigli:

Utilizzare un pool di thread a dimensione fissa Executors.newFixedThreadPool o ThreadPoolExecutor. con un numero massimo stabilito di thread;

Se non sei preoccupato per la coda illimitata di attività Callable / Runnable , puoi usarne una. Come suggerito da Bruno, anch’io preferisco newFixedThreadPool a newCachedThreadPool tra questi due.

Ma ThreadPoolExecutor offre funzionalità più flessibili rispetto a newFixedThreadPool o newCachedThreadPool

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

vantaggi:

  1. Hai il pieno controllo sulla dimensione di BlockingQueue . Non è illimitato a differenza delle precedenti due opzioni. Non esco dall’errore di memoria dovuto all’enorme pila di attività In attesa / eseguibili in attesa di turbolenze inaspettate nel sistema.

  2. È ansible implementare criteri di gestione del rifiuto personalizzati OPPURE utilizzare una delle politiche:

    1. Nel file ThreadPoolExecutor.AbortPolicy predefinito, il gestore ThreadPoolExecutor.AbortPolicy una RejectedExecutionException di runtime in caso di rifiuto.

    2. In ThreadPoolExecutor.CallerRunsPolicy , il thread che richiama execute esegue l’attività. Ciò fornisce un semplice meccanismo di controllo di feedback che rallenta la velocità con cui vengono inviate nuove attività.

    3. In ThreadPoolExecutor.DiscardPolicy , un’attività che non può essere eseguita viene semplicemente eliminata.

    4. In ThreadPoolExecutor.DiscardOldestPolicy , se l’executor non viene arrestato, l’attività in testa alla coda di lavoro viene interrotta e quindi l’esecuzione viene ritentata (che può fallire nuovamente, provocando la ripetizione).

  3. È ansible implementare la fabbrica di fili personalizzata per i casi di utilizzo seguenti:

    1. Per impostare un nome di thread più descrittivo
    2. Per impostare lo stato del daemon del thread
    3. Per impostare la priorità del thread

È necessario utilizzare newCachedThreadPool solo quando si hanno attività asincrone di breve durata come indicato in Javadoc, se si inviano attività che richiedono più tempo per l’elaborazione, si finirà per creare troppi thread. Puoi colpire al 100% la CPU se invii attività a lunga esecuzione a una velocità maggiore a newCachedThreadPool ( http://rashcoder.com/be-careful-while-using-executors-newcachedthreadpool/ ).

Faccio alcuni test rapidi e ho i seguenti risultati:

1) se si utilizza SynchronousQueue:

Dopo che i thread hanno raggiunto la dimensione massima, qualsiasi nuova opera verrà rifiutata con l’eccezione come sotto.

Eccezione nel thread “main” java.util.concurrent.RejectedExecutionException: Task [email protected] rifiutato da [email protected] [Running, dimensione del pool = 3, thread attivi = 3, attività in coda = 0, attività completate = 0]

a java.util.concurrent.ThreadPoolExecutor $ AbortPolicy.rejectedExecution (ThreadPoolExecutor.java:2047)

2) se si utilizza LinkedBlockingQueue:

I thread non aumentano mai dalla dimensione minima alla dimensione massima, il che significa che il pool di thread ha dimensioni fisse come dimensione minima.