Executors.newCachedThreadPool () contro Executors.newFixedThreadPool ()


Risposte:


202

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 ogni momento, al massimo i thread di nThreads saranno attività di elaborazione attive. Se vengono inviate attività aggiuntive quando tutti i thread sono attivi, attenderanno in coda fino a quando non sarà disponibile un thread. Se un thread termina a causa di un errore durante l'esecuzione prima dell'arresto, ne sostituirà uno nuovo, se necessario, per eseguire le attività successive. I thread nel pool esisteranno fino a quando non verranno esplicitamente arrestati.

newCachedThreadPool

Crea un pool di thread che crea nuovi thread secondo necessità, ma riutilizza i thread precedentemente creati quando sono disponibili. Questi pool in genere miglioreranno le prestazioni dei programmi che eseguono molte attività asincrone di breve durata. Le chiamate da eseguire riutilizzeranno i thread precedentemente creati, se disponibili. Se non è disponibile alcun thread esistente, verrà creato e aggiunto un nuovo thread al pool. I thread che non sono stati utilizzati per sessanta secondi vengono chiusi e rimossi dalla cache. Pertanto, un pool che rimane inattivo abbastanza a lungo non consumerà alcuna risorsa. Si noti che i pool con proprietà simili ma dettagli diversi (ad esempio parametri di timeout) possono essere creati utilizzando i costruttori ThreadPoolExecutor.

In termini di risorse, newFixedThreadPoolmanterrà tutti i thread in esecuzione fino a quando non vengono esplicitamente terminati. Nei newCachedThreadPoolthread che non sono stati utilizzati per sessanta secondi vengono chiusi e rimossi dalla cache.

Detto questo, il consumo di risorse dipenderà molto dalla situazione. Ad esempio, se hai un numero enorme di attività di lunga durata, suggerirei il FixedThreadPool. Per quanto riguarda il CachedThreadPooldocumento, i documenti affermano che "Questi pool in genere miglioreranno le prestazioni dei programmi che eseguono molte attività asincrone di breve durata".


1
sì, ho esaminato i documenti..il problema è ... fixedThreadPool sta causando un errore di memoria insufficiente @ 3 thread .. dove cachedPool sta creando internamente un solo thread..on aumentando le dimensioni dell'heap sto ottenendo lo stesso spettacolo per entrambi..è qualcos'altro che mi manca !!
hakish,

1
Stai fornendo Threadfactory a ThreadPool? La mia ipotesi è che nei thread potrebbe essere memorizzato uno stato che non viene raccolto. Altrimenti, forse il tuo programma è in esecuzione così vicino alla dimensione del limite di heap che con la creazione di 3 thread provoca un OutOfMemory. Inoltre, se cachedPool sta creando internamente solo un singolo thread, ciò indica che le attività sono in esecuzione sincronizzate.
bruno conde,

@brunoconde Come sottolineato da @Louis F. ciò newCachedThreadPoolpotrebbe causare seri problemi perché si lascia tutto il controllo su thread poole quando il servizio funziona con altri nello stesso host , il che potrebbe causare l'arresto anomalo degli altri a causa di una lunga attesa della CPU. Quindi penso che newFixedThreadPoolpossa essere più sicuro in questo tipo di scenario. Anche questo post chiarisce le differenze più rilevanti tra loro.
Sentito

75

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

"La scelta del servizio di esecuzione per una particolare applicazione può essere complicata. Se stai scrivendo un piccolo programma o un server leggermente carico , usare Executors.new- CachedThreadPool è generalmente una buona scelta , poiché non richiede alcuna configurazione e in genere" cosa giusta." Ma un pool di thread nella cache non è una buona scelta per un server di produzione pesantemente caricato !

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

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


15

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

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

1
Esatto, un esecutore di thread nella cache con un limite superiore sano e dire, 5-10 minuti di raccolta inattiva è semplicemente perfetto per la maggior parte delle occasioni.
Agoston Horvath,

12

Se non si è preoccupati di una coda illimitata di attività richiamabili / eseguibili , è possibile utilizzarne una. Come suggerito da bruno, anch'io preferisco newFixedThreadPooldi newCachedThreadPoolpiù di questi due.

Ma ThreadPoolExecutor offre funzionalità più flessibili rispetto a uno newFixedThreadPoolonewCachedThreadPool

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

vantaggi:

  1. Hai il pieno controllo delle dimensioni di BlockingQueue . Non è illimitato, a differenza delle due precedenti opzioni. Non otterrò un errore di memoria esaurita a causa di un enorme accumulo di attività Callable / Runnable in sospeso quando c'è turbolenza imprevista nel sistema.

  2. È possibile implementare criteri di gestione dei rifiuti personalizzati O utilizzare uno dei criteri:

    1. Per impostazione predefinita ThreadPoolExecutor.AbortPolicy, il gestore genera un runtime RejectedExecutionException al momento del rifiuto.

    2. In ThreadPoolExecutor.CallerRunsPolicy, il thread che invoca si esegue da solo esegue l'attività. Ciò fornisce un semplice meccanismo di controllo del feedback che rallenterà la velocità di invio di nuove attività.

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

    4. In ThreadPoolExecutor.DiscardOldestPolicy, se l'esecutore non viene arrestato, l'attività in testa alla coda di lavoro viene eliminata e quindi l'esecuzione viene ritentata (il che può non riuscire di nuovo, provocando la ripetizione).

  3. È possibile implementare una factory di thread personalizzata per i casi d'uso seguenti:

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

11

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

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

  1. Non ha limiti, il che significa che stai aprendo la porta a chiunque possa paralizzare la tua JVM semplicemente iniettando più lavoro nel servizio (attacco DoS). I thread consumano una quantità non trascurabile di memoria e aumentano anche il consumo di memoria in base al loro lavoro in corso, quindi è abbastanza facile rovesciare un server in questo modo (a meno che non siano presenti altri interruttori).

  2. Il problema illimitato è esacerbato dal fatto che l'Executor è fronteggiato da un SynchronousQueueche significa che c'è un passaggio 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 cattiva 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 creati più thread, quindi le attività richiedono sempre più tempo per essere completate. Quando la CPU è satura, più thread sicuramente non sono ciò di cui il server ha bisogno.

Ecco i miei consigli:

Utilizzare un pool di thread di dimensioni fisse Executors.newFixedThreadPool o ThreadPoolExecutor. con un numero massimo di thread impostato;


6

La ThreadPoolExecutorclasse è l'implementazione di base per gli esecutori che vengono restituiti da molti Executorsmetodi di fabbrica. Quindi affrontiamo i pool di thread fissi e memorizzati nella cache daThreadPoolExecutor prospettiva.

ThreadPoolExecutor

Il costruttore principale di questa classe si presenta così:

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

Dimensione del pool principale

Il corePoolSizedetermina la dimensione minima del pool di thread di destinazione.L'implementazione manterrebbe un pool di quelle dimensioni anche se non ci sono attività da eseguire.

Dimensione massima della piscina

Il maximumPoolSizeè il numero massimo di thread che possono essere attivi contemporaneamente.

Dopo che il pool di thread cresce e diventa più grande della corePoolSizesoglia, l'esecutore può terminare i thread inattivi e raggiungere corePoolSizenuovamente. Se allowCoreThreadTimeOutè vero, allora l'esecutore può persino terminare i thread del pool core se erano inattivi più della keepAliveTimesoglia.

Quindi la linea di fondo è se i thread rimangono inattivi più della keepAliveTimesoglia, potrebbero essere chiusi poiché non è richiesta per loro.

Fare la fila

Cosa succede quando arriva una nuova attività e tutti i thread principali sono occupati? Le nuove attività verranno messe in coda all'interno di tale BlockingQueue<Runnable>istanza. Quando un thread diventa libero, è possibile elaborare una di quelle attività in coda.

Esistono diverse implementazioni BlockingQueuedell'interfaccia in Java, quindi possiamo implementare diversi approcci di accodamento come:

  1. Coda limitata : le nuove attività verrebbero messe in coda all'interno di una coda attività limitata.

  2. Coda senza limiti : le nuove attività verrebbero messe in coda all'interno di una coda di attività senza limiti. Quindi questa coda può crescere fino a quando la dimensione dell'heap lo consente.

  3. Handoff sincrono : possiamo anche usare il SynchronousQueueper mettere in coda le nuove attività. In tal caso, quando si mette in coda una nuova attività, un altro thread deve già essere in attesa di tale attività.

Presentazione del lavoro

Ecco come ThreadPoolExecutoresegue una nuova attività:

  1. Se inferiore a corePoolSize sono in esecuzione thread, tenta di avviare un nuovo thread con l'attività specificata come primo lavoro.
  2. Altrimenti, tenta di accodare la nuova attività utilizzando il BlockingQueue#offermetodo Il offermetodo non si bloccherà se la coda è piena e ritorna immediatamentefalse .
  3. Se non riesce a mettere in coda la nuova attività (ovvero offerrestituiscefalse ), quindi tenta di aggiungere un nuovo thread al pool di thread con questa attività come primo lavoro.
  4. Se non riesce ad aggiungere il nuovo thread, l'esecutore viene chiuso o saturo. In entrambi i casi, la nuova attività verrebbe rifiutata utilizzando la funzione fornita RejectedExecutionHandler.

La differenza principale tra i pool di thread fissi e memorizzati nella cache si riduce a questi tre fattori:

  1. Dimensione del pool principale
  2. Dimensione massima della piscina
  3. Fare la fila
+ ----------- + ----------- + ------------------- + ----- ---------------------------- +
| Tipo di piscina | Dimensione del nucleo | Dimensione massima | Strategia di accodamento |
+ ----------- + ----------- + ------------------- + ----- ---------------------------- +
| Risolto | n (fisso) | n (fisso) | `LinkedBlockingQueue` senza limiti |
+ ----------- + ----------- + ------------------- + ----- ---------------------------- +
| Memorizzato nella cache | 0 | Integer.MAX_VALUE | `SynchronousQueue` |
+ ----------- + ----------- + ------------------- + ----- ---------------------------- +


Pool di thread fisso


Ecco come Excutors.newFixedThreadPool(n)funziona:

public static ExecutorService newFixedThreadPool(int nThreads) {
    return new ThreadPoolExecutor(nThreads, nThreads,
                                  0L, TimeUnit.MILLISECONDS,
                                  new LinkedBlockingQueue<Runnable>());
}

Come potete vedere:

  • La dimensione del pool di thread è fissa.
  • Se c'è una forte domanda, non crescerà.
  • Se i thread sono inattivi per un po 'di tempo, non si restringono.
  • Supponiamo che tutti quei thread siano occupati con compiti di lunga durata e che il tasso di arrivo sia ancora piuttosto alto. Poiché l'esecutore utilizza una coda illimitata, potrebbe consumare una parte enorme dell'heap. Essendo abbastanza sfortunato, potremmo sperimentare un OutOfMemoryError.

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

Un pool di thread di dimensioni fisse sembra essere un buon candidato quando limiteremo il numero di attività simultanee ai fini della gestione delle risorse .

Ad esempio, se utilizzeremo un esecutore per gestire le richieste del server Web, un esecutore fisso può gestire i burst della richiesta in modo più ragionevole.

Per una gestione delle risorse ancora migliore, si consiglia vivamente di creare un'abitudine ThreadPoolExecutorcon BlockingQueue<T>un'implementazione limitata abbinata a ragionevole RejectedExecutionHandler.


Pool di thread memorizzati nella cache


Ecco come Executors.newCachedThreadPool()funziona:

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

Come potete vedere:

  • Il pool di thread può crescere da zero thread a Integer.MAX_VALUE. In pratica, il pool di thread è illimitato.
  • Se un thread rimane inattivo per più di 1 minuto, potrebbe terminare. Quindi il pool può ridursi se i thread rimangono troppo inattivi.
  • Se tutti i thread allocati sono occupati mentre arriva una nuova attività, allora crea un nuovo thread, poiché l'offerta di una nuova attività a una SynchronousQueuefallisce sempre quando non c'è nessuno all'altra estremità per accettarla!

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

Usalo quando hai molte attività prevedibili di breve durata.



1

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, ad eccezione di quella indicata di seguito.

Eccezione nel thread "main" java.util.concurrent.RejectedExecutionException: Task java.util.concurrent.FutureTask@3fee733d rifiutata da java.util.concurrent.ThreadPoolExecutor@5acf9800 [Esecuzione, dimensioni pool = 3, thread attivi = 3, attività in coda = 0, attività completate = 0]

at 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.

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.