FixedThreadPool vs CachedThreadPool: o menor dos dois males

93

Tenho um programa que gera threads (~ 5-150) que realizam várias tarefas. Originalmente, usei um FixedThreadPoolporque essa pergunta semelhante sugeria que eles eram mais adequados para tarefas de longa duração e, com meu conhecimento muito limitado de multithreading, considerei a vida média dos threads (vários minutos) de " vida longa ".

No entanto, recentemente adicionei a capacidade de gerar threads adicionais e isso me leva acima do limite de threads que defini. Nesse caso, seria melhor adivinhar e aumentar o número de encadeamentos que posso permitir ou alternar para um CachedThreadPoolpara que não tenha encadeamentos desperdiçados?

Experimentando os dois preliminarmente, não parece haver diferença, então estou inclinado a ir com o CachedThreadPoolapenas para evitar o desperdício. No entanto, a vida útil dos fios significa que, em vez disso, devo escolher um FixedThreadPoole apenas lidar com os fios não utilizados? Esta pergunta faz parecer que esses tópicos extras não foram desperdiçados, mas agradeceria o esclarecimento.

Daniel
fonte

Respostas:

108

Um CachedThreadPool é exatamente o que você deve usar para sua situação, já que não há consequência negativa em usar um para threads de longa execução. O comentário no documento java sobre CachedThreadPools ser adequado para tarefas curtas apenas sugere que eles são particularmente apropriados para tais casos, não que eles não possam ou não devam ser usados ​​para tarefas envolvendo tarefas de longa execução.

Para elaborar mais, Executors.newCachedThreadPool e Executors.newFixedThreadPool são ambos apoiados pela mesma implementação de pool de threads (pelo menos no JDK aberto) apenas com parâmetros diferentes. As diferenças são apenas seu thread mínimo, máximo, tempo de interrupção do thread e tipo de fila.

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

Um FixedThreadPool tem suas vantagens quando você de fato deseja trabalhar com um número fixo de threads, já que então você pode enviar qualquer número de tarefas ao serviço do executor sabendo que o número de threads será mantido no nível especificado. Se você deseja explicitamente aumentar o número de threads, esta não é a escolha apropriada.

No entanto, isso significa que o único problema que você pode ter com o CachedThreadPool é em relação à limitação do número de threads que estão sendo executados simultaneamente. O CachedThreadPool não os limitará para você, portanto, pode ser necessário escrever seu próprio código para garantir que não execute muitos threads. Isso realmente depende do design do seu aplicativo e como as tarefas são enviadas ao serviço do executor.

Trevor Freeman
fonte
1
"Um CachedThreadPool é exatamente o que você deve usar para sua situação, já que não há conseqüências negativas em usar um para threads de longa execução". Eu não acho que concordo. CachedThreadPool cria threads dinamicamente sem limite superior. Tarefas de longa execução em um grande número de threads podem potencialmente monopolizar todos os recursos. Além disso, ter mais threads do que o ideal pode resultar em muitos recursos desperdiçados na troca de contexto desses threads. Embora você tenha explicado no final da resposta que a limitação personalizada é necessária, o início da resposta é um pouco enganador.
Nishit
1
Por que não simplesmente criar um ThreadPoolExecutorgosto limitado ThreadPoolExecutor(0, maximumPoolSize, 60L, TimeUnit.SECONDS, SynchronousQueue())?
Abhijit Sarkar
45

Ambos FixedThreadPoole CachedThreadPoolsão males em aplicativos altamente carregados.

CachedThreadPool é mais perigoso que FixedThreadPool

Se seu aplicativo estiver altamente carregado e exigir baixa latência, é melhor se livrar de ambas as opções devido às desvantagens abaixo

  1. Natureza ilimitada da fila de tarefas: pode causar falta de memória ou alta latência
  2. CachedThreadPoolThreads de longa execução irão causar a perda de controle na criação de threads

Já que você sabe que ambos são males, o mal menor não faz bem. Prefira ThreadPoolExecutor , que fornece controle granular em muitos parâmetros.

  1. Defina a fila de tarefas como fila limitada para ter melhor controle
  2. Tenha RejectionHandler correto - seu próprio RejectionHandler ou gerenciadores padrão fornecidos por JDK
  3. Se você tiver algo para fazer antes / depois da conclusão da tarefa, substitua beforeExecute(Thread, Runnable)eafterExecute(Runnable, Throwable)
  4. Substituir ThreadFactory se a personalização de thread for necessária
  5. Controlar o tamanho do pool de threads dinamicamente em tempo de execução (pergunta SE relacionada: Pool de threads dinâmicos )
Ravindra babu
fonte
E se alguém decidir usar o commonPool?
Crosk Cool
1
@Ravindra - Você explicou lindamente os contras de CachedThreadPool e FixedThreadPool. Isso mostra que você tem um conhecimento profundo do pacote de simultaneidade.
Ayaskant
5

Portanto, tenho um programa que gera threads (~ 5-150) que realizam várias tarefas.

Tem certeza de que entende como os threads são realmente processados ​​pelo seu sistema operacional e hardware de escolha? Como Java mapeia threads para threads de SO, como isso mapeia threads para threads de CPU etc.? Estou perguntando porque a criação de 150 threads em ONE JRE só faz sentido se você tiver núcleos / threads de CPU massivos por baixo, o que provavelmente não é o caso. Dependendo do sistema operacional e da RAM em uso, a criação de mais de n threads pode até mesmo resultar no encerramento do JRE devido a erros de OOM. Portanto, você realmente deve distinguir entre os threads e o trabalho a fazer por esses threads, quantos trabalhos você é capaz de processar, etc.

E esse é o problema com CachedThreadPool: não faz sentido enfileirar trabalhos de longa execução em threads que na verdade não podem ser executados porque você só tem 2 núcleos de CPU capazes de processar essas threads. Se você terminar com 150 threads agendados, poderá criar muita sobrecarga desnecessária para os agendadores usados ​​no Java e no sistema operacional para processá-los simultaneamente. Isso é simplesmente impossível se você tiver apenas 2 núcleos de CPU, a menos que seus threads estejam esperando por E / S ou algo parecido o tempo todo. Mas mesmo nesse caso, muitos encadeamentos criariam muito I / O ...

E esse problema não ocorre com FixedThreadPool, criado com, por exemplo, 2 + n threads, onde n é razoavelmente baixo, é claro, porque com esse hardware e os recursos do sistema operacional são usados ​​com muito menos sobrecarga para gerenciar threads que não podem ser executados de qualquer maneira.

Thorsten Schöning
fonte
Às vezes não há uma escolha melhor, você poderia ter apenas 1 núcleo de CPU, mas se você estiver executando um servidor onde cada solicitação do usuário acionaria um thread para processar a solicitação, não haverá nenhuma outra escolha razoável, especialmente se você planeja para dimensionar o servidor quando você aumentar sua base de usuários.
Michel Feinstein,
@mFeinstein Como alguém pode não ter escolha se está em posição de escolher uma implementação de pool de threads? Em seu exemplo com 1 núcleo de CPU gerando apenas mais threads simplesmente não faz sentido, está se encaixando perfeitamente no meu exemplo usando um FixedThreadPool. Isso também pode ser escalado facilmente, primeiro com um ou dois threads de trabalho, depois com 10 ou 15 dependendo do número de núcleos.
Thorsten Schöning,
2
A grande maioria das implementações de servidores web criará um novo thread para cada nova solicitação HTTP ... Eles não se importam com quantos núcleos reais a máquina tem, isso torna a implementação mais simples e fácil de escalar. Isso se aplica a muitos outros projetos em que você deseja apenas codificar uma vez e implantar, e não precisa recompilar e reimplantar se alterar a máquina, que pode ser uma instância de nuvem.
Michel Feinstein,
@mFeinstein A maioria dos servidores web usa pools de thread para solicitações por conta própria, simplesmente porque a geração de threads que não podem ser executados não faz sentido, ou eles usam loops de eventos para conexões e processam as solicitações em pools depois disso. Além disso, você está perdendo o ponto, que a questão é sobre ser capaz de escolher o pool de threads correto e gerar threads que não podem ser executados de forma alguma ainda não faz sentido. Um FixedthreadPool configurado para uma quantidade razoável de threads por máquina dependendo das escalas de núcleos muito bem.
Thorsten Schöning,
3
@ ThorstenSchöning, ter 50 threads vinculados à CPU em uma máquina de 2 núcleos não ajuda muito. Ter 50 threads vinculados a IO em uma máquina de 2 núcleos pode ser muito útil.
Paul Draper