A criação excessiva de threads consome memória significativa e aumenta a sobrecarga do sistema devido às frequentes trocas de contexto. Para mitigar esses problemas, utiliza-se o conceito de Thread Pools (Pools de Threads), uma tecnologia baseada na reutilização e pré-alocação de recursos. Um pool gerencia um conjunto unificado de threads, otimizando o tempo de resposta e a utilização de recursos do sistema.
Embora o Java 21 tenha introduzido as Virtual Threads e o Go utilize Goroutines para resolver problemas de desempenho relacionados à criação de threads, essas abordagens não se baseiam na ideia de pool (alocação prévia), mas sim em mecanismos de agendamento de tarefas altamente eficientes. O foco deste artigo é aprofundar a arquitetura e a configuração dos Thread Pools tradicionais no ecossistema Java.
Configuração Personalizada de Thread Pools
A instanciação de um pool de threads no Java é realizada através do construtor da classe ThreadPoolExecutor. Abaixo está uma representação reescrita de sua assinatura e lógica de validação:
public CustomThreadPoolExecutor(int coreSize,
int maxSize,
long idleTimeout,
TimeUnit timeUnit,
BlockingQueue<Runnable> taskQueue,
ThreadFactory factory,
RejectedExecutionHandler rejectionPolicy) {
if (coreSize < 0 || maxSize <= 0 || maxSize < coreSize || idleTimeout < 0) {
throw new IllegalArgumentException("Parâmetros de configuração inválidos.");
}
if (taskQueue == null || factory == null || rejectionPolicy == null) {
throw new NullPointerException("Componentes essenciais não podem ser nulos.");
}
this.coreSize = coreSize;
this.maxSize = maxSize;
this.taskQueue = taskQueue;
this.idleTimeout = timeUnit.toNanos(idleTimeout);
this.factory = factory;
this.rejectionPolicy = rejectionPolicy;
}
Os parâmetros essenciais para a configuração de um pool de threads são detalhados na tabela a seguir:
| Parâmetro | Descrição | Função | Obrigatório |
|---|---|---|---|
| coreSize | Quantidade de threads do núcleo | Threads ativas para execução. Novas threads são criadas se o número atual for menor que este valor e houver tarefas pendentes. | Sim |
| maxSize | Quantidade máxima de threads | Limite superior de threads. Novas threads além do núcleo são criadas se a fila de tarefas estiver cheia e o número atual for menor que este valor. | Sim |
| idleTimeout | Tempo de inatividade | Threads excedentes (acima do núcleo) que ficarem ociosas por este período serão encerradas. | Sim |
| timeUnit | Unidade de tempo | Define a escala temporal para o parâmetro de inatividade. | Sim |
| taskQueue | Fila de tarefas | Estrutura de dados para armazenar tarefas aguardando execução. | Sim |
| factory | Fábrica de threads | Mecanismo responsável por instanciar novas threads. | Não |
| rejectionPolicy | Política de rejeição | Ação tomada quando o pool atinge o tamanho máximo e a fila está cheia. | Não |
Quando o pool atinge sua capacidade máxima e a fila de tarefas está cheia, a política de rejeição é acionada. As estratégias padrão disponíveis são:
| Estratégia | Comportamento | Cenário de Uso |
|---|---|---|
| CallerRunsPolicy | A thread que chamou a execução do pool passa a processar a tarefa diretamente, desde que o pool não esteja encerrado. | Cenários com baixa exigência de concorrência. Pode impactar o desempenho da thread chamadora. |
| AbortPolicy | Descarta a tarefa e lança uma exceção RejectedExecutionException. |
Quando o desenvolvedor precisa interceptar e tratar a rejeição manualmente. |
| DiscardPolicy | Descarta a tarefa silenciosamente, sem gerar exceções. | Tarefas consideradas não críticas ou descartáveis. |
| DiscardOldestPolicy | Remove a tarefa mais antiga da fila de espera e tenta enfileirar a nova tarefa novamente. | Quando as tarefas mais recentes possuem maior prioridade que as antigas. |
Implementações Padrão de Thread Pools no Java
Pool de Tamanho Fixo (FixedThreadPool)
Esta implementação cria um pool com um número estritamente definido de threads.
public static ExecutorService criarPoolFixo(int tamanhoFixo) {
return new ThreadPoolExecutor(tamanhoFixo, tamanhoFixo,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
Analisando os parâmetros, observa-se que o tamanho do núcleo e o máximo são idênticos, e a fila de tarefas é ilimitada (LinkedBlockingQueue). Isso significa que não há uma política de rejeição padrão, pois a fila continuará crescendo. Se a taxa de chegada de tarefas superar a capacidade de processamento, o consumo de memória pode levar a um erro de OutOfMemoryError.
Pool Dinâmico (CachedThreadPool)
Projetado para lidar com um grande número de tarefas de curta duração.
public static ExecutorService criarPoolDinamico() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
Nesta configuração, o tamanho do núcleo é zero, enquanto o tamanho máximo é o maior valer inteiro possível. A fila utilizada é a SynchronousQueue, que não possui capacidade de armazenamento interno. Consequentemente, cada tarefa recebida exige a criação imediata de uma nova thread, a menos que uma thread ociosa esteja disponível. É ideal para tarefas rápidas, mas pode levar à exaustão de recursos do sistema se utilizado para processos longos.
Pool de Thread Única (SingleThreadExecutor)
Utilizado quando as tarefas precisam ser executadas estritamente em ordem sequencial.
public static ExecutorService criarPoolUnico() {
return new FinalizableDelegatedExecutorService(
new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
Assim como o pool fixo, este utiliza uma fila ilimitada, o que requer atenção para evitar estouro de memória. A garantia principal é que apenas uma thread estará ativa processando as tarefas da fila por vez.
Pool Agendado (ScheduledThreadPool)
Focado na execução de tarefas periódicas ou com atraso programado.
public AgendadorThreadPool(int tamanhoNucleo) {
super(tamanhoNucleo, Integer.MAX_VALUE, 0, NANOSECONDS,
new DelayedWorkQueue());
}
Diferente da classe legada Timer, onde uma exceção ou atraso em uma tarefa pode afetar o agendamento das demais, o ScheduledThreadPool isola as falhas. Se uma tarefa lançar uma exceção, as outras continuam sendo executadas normalmente, tornando-o a escolha recomendada para cenários de agendamento concorrente.