newCachedThreadPool()
versus newFixedThreadPool()
Quando devo usar um ou outro? Qual estratégia é melhor em termos de utilização de recursos?
newCachedThreadPool()
versus newFixedThreadPool()
Quando devo usar um ou outro? Qual estratégia é melhor em termos de utilização de recursos?
Respostas:
Acho que os documentos explicam muito bem a diferença e o uso dessas duas funções:
Cria um conjunto de encadeamentos que reutiliza um número fixo de encadeamentos que operam em uma fila ilimitada compartilhada. A qualquer momento, no máximo os nThreads threads serão tarefas de processamento ativas. Se tarefas adicionais forem enviadas quando todos os encadeamentos estiverem ativos, eles aguardarão na fila até que um encadeamento esteja disponível. Se algum encadeamento terminar devido a uma falha durante a execução anterior ao encerramento, um novo substituirá, se necessário, para executar tarefas subseqüentes. Os encadeamentos no pool existirão até que seja explicitamente encerrado.
Cria um conjunto de encadeamentos que cria novos encadeamentos, conforme necessário, mas reutiliza encadeamentos construídos anteriormente quando estiverem disponíveis. Esses pools geralmente melhoram o desempenho dos programas que executam muitas tarefas assíncronas de curta duração. As chamadas a serem executadas reutilizarão os threads construídos anteriormente, se disponíveis. Se nenhum encadeamento existente estiver disponível, um novo encadeamento será criado e adicionado ao pool. Os segmentos que não foram usados por sessenta segundos são encerrados e removidos do cache. Portanto, um pool que permanecer ocioso por tempo suficiente não consumirá nenhum recurso. Observe que conjuntos com propriedades semelhantes, mas detalhes diferentes (por exemplo, parâmetros de tempo limite) podem ser criados usando os construtores ThreadPoolExecutor.
Em termos de recursos, o newFixedThreadPool
manterá todos os threads em execução até que sejam explicitamente finalizados. Nos newCachedThreadPool
Threads que não foram usados por sessenta segundos, são encerrados e removidos do cache.
Diante disso, o consumo de recursos dependerá muito da situação. Por exemplo, se você tiver um grande número de tarefas de execução longa, sugiro o FixedThreadPool
. Quanto CachedThreadPool
aos documentos, os documentos dizem que "esses pools geralmente melhoram o desempenho de programas que executam muitas tarefas assíncronas de curta duração".
newCachedThreadPool
pode causar alguns problemas sérios , porque você deixa todo o controle para thread pool
e quando o serviço está trabalhando com outras pessoas no mesmo host , o que pode causar a falha de outras devido à espera de CPU por muito tempo. Então, acho que newFixedThreadPool
pode ser mais seguro nesse tipo de cenário. Além disso, este post esclarece as diferenças mais marcantes entre eles.
Apenas para completar as outras respostas, gostaria de citar Effective Java, 2nd Edition, de Joshua Bloch, capítulo 10, Item 68:
"A escolha do serviço executor para um aplicativo específico pode ser complicada. Se você estiver escrevendo um programa pequeno ou um servidor com pouca carga , usando Executors.new- CachedThreadPool geralmente é uma boa opção , pois não requer configuração e geralmente" faz o coisa certa." Mas um conjunto de encadeamentos em cache não é uma boa opção para um servidor de produção muito carregado !
Em um conjunto de encadeamentos em cache , as tarefas enviadas não são enfileiradas, mas são imediatamente entregues a um encadeamento para execução. Se nenhum encadeamento estiver disponível, um novo será criado . Se um servidor estiver tão carregado que todas as suas CPUs forem totalmente utilizadas e mais tarefas chegarem, mais threads serão criados, o que só tornará as coisas piores.
Portanto, em um servidor de produção muito carregado , é muito melhor usar Executors.newFixedThreadPool , que fornece um pool com um número fixo de threads ou usar a classe ThreadPoolExecutor diretamente, para obter o máximo controle. "
Se você olhar o código-fonte , verá que eles estão chamando ThreadPoolExecutor. internamente e definindo suas propriedades. Você pode criar o seu para ter um melhor controle de seus requisitos.
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>());
}
Se você não estiver preocupado com uma fila ilimitada de tarefas Chamadas / Executáveis , poderá usar uma delas. Como sugerido por bruno, eu também prefiro newFixedThreadPool
que newCachedThreadPool
ao longo destes dois.
Mas ThreadPoolExecutor fornece recursos mais flexíveis em relação a um ou outro newFixedThreadPool
ounewCachedThreadPool
ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime,
TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory,
RejectedExecutionHandler handler)
Vantagens:
Você tem controle total do tamanho do BlockingQueue . Não é ilimitado, ao contrário das duas opções anteriores. Não receberei um erro de falta de memória devido a um grande acúmulo de tarefas pendentes de chamar / executar quando houver turbulência inesperada no sistema.
Você pode implementar uma política de tratamento de rejeição personalizada OU usar uma das políticas:
Por padrão ThreadPoolExecutor.AbortPolicy
, o manipulador lança um RejectedExecutionException em tempo de execução após a rejeição.
Em ThreadPoolExecutor.CallerRunsPolicy
, o encadeamento que chama a execução executa a tarefa. Isso fornece um mecanismo simples de controle de feedback que diminuirá a taxa de envio de novas tarefas.
Em ThreadPoolExecutor.DiscardPolicy
, uma tarefa que não pode ser executada é simplesmente descartada.
Em ThreadPoolExecutor.DiscardOldestPolicy
, se o executor não for desligado, a tarefa na cabeça da fila de trabalho será descartada e a execução será tentada novamente (o que pode falhar novamente, fazendo com que isso se repita).
Você pode implementar uma fábrica de threads personalizada para os casos de uso abaixo:
Isso mesmo, Executors.newCachedThreadPool()
não é uma ótima opção para código de servidor que atende a vários clientes e solicitações simultâneas.
Por quê? Existem basicamente dois problemas relacionados:
É ilimitado, o que significa que você está abrindo a porta para alguém prejudicar sua JVM simplesmente injetando mais trabalho no serviço (ataque de DoS). Os threads consomem uma quantidade não desprezível de memória e também aumentam o consumo de memória com base no trabalho em andamento; portanto, é muito fácil derrubar um servidor dessa maneira (a menos que você tenha outros disjuntores instalados).
O problema ilimitado é exacerbado pelo fato de o Executor ser liderado por um, o SynchronousQueue
que significa que há uma transferência direta entre o responsável pela tarefa e o pool de threads. Cada nova tarefa criará um novo thread se todos os threads existentes estiverem ocupados. Geralmente, essa é uma estratégia ruim para o código do servidor. Quando a CPU fica saturada, as tarefas existentes levam mais tempo para serem concluídas. Ainda mais tarefas estão sendo enviadas e mais threads são criados, portanto, as tarefas demoram mais e mais para serem concluídas. Quando a CPU está saturada, mais threads definitivamente não é o que o servidor precisa.
Aqui estão as minhas recomendações:
Use um conjunto de encadeamentos de tamanho fixo Executors.newFixedThreadPool ou ThreadPoolExecutor. com um número máximo definido de threads;
A ThreadPoolExecutor
classe é a implementação básica para os executores retornados de muitos dos Executors
métodos de fábrica. Então, vamos abordar os conjuntos de encadeamentos fixos e armazenados em cache da ThreadPoolExecutor
perspectiva.
O construtor principal desta classe é semelhante a este:
public ThreadPoolExecutor(
int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler
)
O corePoolSize
determina o tamanho mínimo do conjunto de encadeamentos de destino. A implementação manteria um pool desse tamanho, mesmo que não haja tarefas a serem executadas.
O maximumPoolSize
é o número máximo de encadeamentos que podem estar ativos ao mesmo tempo.
Depois que o conjunto de encadeamentos cresce e se torna maior que o corePoolSize
limite, o executor pode encerrar encadeamentos ociosos e alcançar o corePoolSize
novo. Se allowCoreThreadTimeOut
for verdade, o executor pode até finalizar os encadeamentos do pool principal se eles estiverem ociosos mais do que o keepAliveTime
limite.
Portanto, o ponto principal é que, se os threads permanecerem ociosos mais do que o keepAliveTime
limite, eles poderão ser encerrados, pois não há demanda por eles.
O que acontece quando uma nova tarefa entra e todos os threads principais são ocupados? As novas tarefas serão enfileiradas dentro dessa BlockingQueue<Runnable>
instância. Quando um encadeamento fica livre, uma dessas tarefas na fila pode ser processada.
Existem implementações diferentes da BlockingQueue
interface em Java, para que possamos implementar abordagens de enfileiramento diferentes, como:
Fila limitada : Novas tarefas seriam colocadas em fila dentro de uma fila de tarefas limitada.
Fila não vinculada : Novas tarefas seriam colocadas em fila dentro de uma fila de tarefas ilimitada. Portanto, essa fila pode crescer tanto quanto o tamanho da pilha permitir.
Entrega Síncrona : Também podemos usar o SynchronousQueue
para enfileirar as novas tarefas. Nesse caso, ao enfileirar uma nova tarefa, outro encadeamento já deve estar esperando por essa tarefa.
Aqui está como ele ThreadPoolExecutor
executa uma nova tarefa:
corePoolSize
threads estiverem em execução, tentará iniciar um novo thread com a tarefa especificada como seu primeiro trabalho.BlockingQueue#offer
método O offer
método não será bloqueado se a fila estiver cheia e retornar imediatamente false
.offer
retornar false
), ele tentará adicionar um novo encadeamento ao pool de encadeamentos com essa tarefa como seu primeiro trabalho.RejectedExecutionHandler
.A principal diferença entre os conjuntos de encadeamentos fixo e em cache se resume a esses três fatores:
+ ----------- + ----------- + ------------------- + ----- ---------------------------- + | Tipo de piscina | Tamanho do núcleo | Tamanho máximo | Estratégia de filas | + ----------- + ----------- + ------------------- + ----- ---------------------------- + | Fixo | n (fixo) | n (fixo) | Não vinculado `LinkedBlockingQueue` | + ----------- + ----------- + ------------------- + ----- ---------------------------- + | Em cache | 0 Inteiro.MAX_VALUE | `SynchronousQueue` | + ----------- + ----------- + ------------------- + ----- ---------------------------- +
Excutors.newFixedThreadPool(n)
funciona:
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
Como você pode ver:
OutOfMemoryError
.Quando devo usar um ou outro? Qual estratégia é melhor em termos de utilização de recursos?
Um conjunto de encadeamentos de tamanho fixo parece ser um bom candidato quando vamos limitar o número de tarefas simultâneas para fins de gerenciamento de recursos .
Por exemplo, se vamos usar um executor para lidar com solicitações de servidor da Web, um executor fixo pode lidar com as explosões de solicitações de maneira mais razoável.
Para um gerenciamento de recursos ainda melhor, é altamente recomendável criar um costume ThreadPoolExecutor
com uma BlockingQueue<T>
implementação limitada juntamente com razoável RejectedExecutionHandler
.
Eis como Executors.newCachedThreadPool()
funciona:
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
Como você pode ver:
Integer.MAX_VALUE
. Praticamente, o conjunto de encadeamentos é ilimitado.SynchronousQueue
sempre falha quando não há ninguém do outro lado para aceitá-la!Quando devo usar um ou outro? Qual estratégia é melhor em termos de utilização de recursos?
Use-o quando você tiver muitas tarefas previsíveis de execução curta.
Você deve usar newCachedThreadPool apenas quando tiver tarefas assíncronas de curta duração, conforme declarado no Javadoc. Se você enviar tarefas que demoram mais tempo para serem processadas, você acabará criando muitos threads. Você pode atingir 100% da CPU se enviar tarefas de execução longa a uma taxa mais rápida para o newCachedThreadPool ( http://rashcoder.com/be-careful- while-using-executors-newcachedthreadpool/ ).
Faço alguns testes rápidos e tenho as seguintes descobertas:
1) se estiver usando SynchronousQueue:
Depois que os encadeamentos atingirem o tamanho máximo, qualquer novo trabalho será rejeitado, com a exceção abaixo.
Exceção no encadeamento "main" java.util.concurrent.RejectedExecutionException: tarefa java.util.concurrent.FutureTask@3fee733d rejeitada de java.util.concurrent.ThreadPoolExecutor@5acf9800 [Em execução, tamanho do pool = 3, segmentos ativos = 3, tarefas em fila = 0, tarefas concluídas = 0]
em java.util.concurrent.ThreadPoolExecutor $ AbortPolicy.rejectedExecution (ThreadPoolExecutor.java:2047)
2) se estiver usando o LinkedBlockingQueue:
Os encadeamentos nunca aumentam do tamanho mínimo para o tamanho máximo, o que significa que o conjunto de encadeamentos é tamanho fixo como o tamanho mínimo.