Ao desenvolver um aplicativo multi-threaded, geralmente devemos lidar com a organização do trabalho dos threads. Quanto maior for nosso aplicativo e quanto mais threads precisarmos para tarefas multithread, maisExecutávelobjetos que criamos.

Deve-se notar aqui que criar um thread em Java é uma operação bastante cara. Se criarmos uma nova instância do thread a cada vez para realizar uma operação, teremos grandes problemas com o desempenho e, como resultado, com a integridade do aplicativo.

Um pool de threads e ThreadPoolExecutor vêm em nosso auxílio aqui.

Um pool de threads é um conjunto de threads pré-inicializados. Seu tamanho pode ser fixo ou variável.

Se houver mais tarefas do que threads, as tarefas aguardarão em uma fila de tarefas. O enésimo encadeamento no pool pega uma tarefa da fila e, depois de concluído, o encadeamento pega uma nova tarefa da fila. Depois que todas as tarefas na fila são executadas, os threads permanecem ativos e aguardam novas tarefas. Quando novas tarefas aparecem, os threads começam a executá-las também.

ThreadPoolExecutor

A partir do Java 5, o framework Executor ganhou uma solução multithreading. Em geral, ele possui muitos componentes e seu objetivo é nos ajudar a gerenciar com eficiência filas e pools de threads.

As interfaces principais são Executor e ExecutorService .

Executor é uma interface com um único método void execute(Runnable runnable).

Ao passar uma tarefa para uma implementação desse método, saiba que futuramente ela será executada de forma assíncrona.

ExecutorService — Uma interface que estende a interface Executor , adicionando recursos para executar tarefas. Ele também possui métodos para interromper uma tarefa em execução e encerrar o pool de encadeamentos.

ThreadPoolExecutor implementa as interfaces Executor e ExecutorService e separa a criação da tarefa da execução da tarefa. Precisamos implementar objetos Runnable e enviá-los para um executor. O ThreadPoolExecutor é então responsável por executar as tarefas, criar e trabalhar com threads.

Depois que uma tarefa é enviada para execução, um thread existente no pool é usado. Isso melhora o desempenho. Ele resolve o problema de desperdício de recursos na criação e inicialização de um novo encadeamento e, novamente, na coleta de lixo quando terminamos o encadeamento.

ThreadPoolExecutor tem 4 construtores:


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

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

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

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

O construtor ThreadPoolExecutor tem os seguintes parâmetros:

corePoolSize Este parâmetro indica quantas threads estarão prontas (iniciadas) quando o serviço executor for iniciado.
MaximumPoolSize O número máximo de encadeamentos que um serviço executor pode criar.
KeepAliveTime O tempo que um thread liberado continuará a viver antes de ser destruído se o número de threads for maior quecorePoolSize. As unidades de tempo são especificadas no próximo parâmetro.
unidade Unidades de tempo (horas, minutos, segundos, milissegundos, etc.).
fila de trabalho Implementação de uma fila de tarefas.
manipulador Manipulador para tarefas que não podem ser concluídas.
threadFactory Um objeto que cria novos threads sob demanda. O uso de fábricas de encadeamentos torna as chamadas para um novo hardware de encadeamento independente, permitindo que os aplicativos usem subclasses de encadeamentos especiais, prioridades e assim por diante.

Criando um ThreadPoolExecutor

A classe de utilitário Executors pode simplificar a criação de um ThreadPoolExecutor . Os métodos desta classe utilitária nos ajudam a preparar umThreadPoolExecutorobjeto.

newFixedThreadPool — Cria um pool de threads que reutiliza um número fixo de threads para executar qualquer número de tarefas.

ExecutorService executor = Executors.newFixedThreadPool(10);
                    
newWorkStealingPool — Cria um conjunto de encadeamentos em que o número de encadeamentos é igual ao número de núcleos de processador disponíveis para a JVM. O nível de simultaneidade padrão é um. Isso significa que tantos encadeamentos serão criados no pool quantos forem os núcleos de CPU disponíveis para a JVM. Se o nível de simultaneidade for 4, o valor passado será usado em vez do número de núcleos.

ExecutorService executor = Executors.newWorkStealingPool(4);
                    
newSingleThreadExecutor — Cria um pool com um único thread para executar todas as tarefas.

ExecutorService executor = Executors.newSingleThreadExecutor();
                    
newCachedThreadPool — Cria um pool de encadeamentos que cria novos encadeamentos conforme necessário, mas reutiliza os encadeamentos criados anteriormente quando eles estão disponíveis.

ExecutorService executor = Executors.newCachedThreadPool();
                    
newScheduledThreadPool — Cria um pool de encadeamentos que pode agendar comandos para execução após um determinado atraso ou periodicamente.

ScheduledExecutorService executor = Executors.newScheduledThreadPool(10);
                    

Vamos considerar cada tipo de piscina nas próximas lições.