Executor: Execução de Tarefas em Java
O Executor representa um objeto responsável pela execução de tarefas. Ele substitui a criação manual de threads e separa o envio de uma tarefa do mecanismo responsável por executá-la.
Esse interface faz parte do pacote java.util.concurrent e possui apenas um método:
void execute(Runnable command)O método execute() recebe uma tarefa Runnable e a executa em algum momento futuro.
Exemplo básico de Executor
import java.util.concurrent.*;
class TaskExecutor implements Executor {
public void execute(Runnable task) {
task.run();
}
}
class Program {
public static void main(String[] args) {
System.out.println("Main thread started...");
var executor = new TaskExecutor();
executor.execute(() -> {
System.out.println("Executing task in " + Thread.currentThread());
});
System.out.println("Main thread finished...");
}
}Neste exemplo, a classe TaskExecutor implementa a interface Executor. O método execute() simplesmente chama o método run() da tarefa recebida, executando-a no mesmo thread que chamou execute() — neste caso, o main.
Esse tipo de execução é síncrona, ou seja, o código da tarefa é executado imediatamente na mesma thread. Embora não traga vantagem prática em relação a chamar diretamente o método run(), o Executor oferece uma estrutura mais flexível que permite implementar diferentes estratégias de execução.
Executor com nova thread
Na maioria dos casos, a execução de tarefas ocorre em threads separadas. O código abaixo mostra como modificar o executor para criar uma nova thread para cada tarefa:
class TaskExecutor implements Executor {
public void execute(Runnable task) {
new Thread(task).start();
}
}Agora, cada chamada a execute() cria e inicia uma nova thread.
A saída de um programa usando esse executor seria semelhante a:
Main thread started... Main thread finished... Executing task in Thread[#34,Thread-0,5,main]
Esse comportamento mostra que a tarefa é executada em um thread diferente do principal.
ExecutorService
A interface ExecutorService estende o Executor, adicionando métodos para controle do ciclo de vida das execuções e suporte a tarefas assíncronas que retornam resultados, representadas pela interface Future.
A principal implementação desse interface é a classe ThreadPoolExecutor, que gerencia um pool de threads — um conjunto de threads reutilizáveis que executam tarefas conforme necessário. O uso de pools melhora o desempenho, pois evita o custo de criar e destruir threads repetidamente.
Criação de ExecutorService
Os executores são criados por meio de métodos estáticos da classe Executors, que oferecem diferentes estratégias de execução:
Executors.newCachedThreadPool()Cria um pool que adiciona novas threads conforme necessário e reutiliza as que estiverem livres. Threads ociosas são mantidas por até 60 segundos.Executors.newFixedThreadPool(int nThreads)Cria um pool com número fixo de threads. Tarefas excedentes aguardam em uma fila até que uma thread esteja disponível.Executors.newSingleThreadExecutor()Cria um executor com uma única thread, que executa as tarefas de forma sequencial.Executors.newVirtualThreadPerTaskExecutor()Cria um executor que executa cada tarefa em uma nova thread virtual, uma funcionalidade introduzida no Java moderno para lidar com alta concorrência de forma leve.
Todos esses métodos retornam um objeto que implementa a interface ExecutorService.
Envio de Tarefas
O envio de tarefas é feito com o método submit(), que aceita objetos Runnable ou Callable:
Future<T> submit(Callable<T> task)
Future<?> submit(Runnable task)
Future<T> submit(Runnable task, T result)O método submit() agenda a execução da tarefa e retorna um objeto Future, que pode ser usado para consultar o estado da execução, obter o resultado ou cancelar a tarefa.
A segunda versão retorna Future<?>, cujo método get() bloqueia até o término da execução e retorna null.
A terceira versão retorna um Future cujo método get() devolve o valor passado no segundo parâmetro, após o término da tarefa.
Encerramento do Executor
Após o uso, o executor deve ser encerrado para liberar recursos. Isso é feito com o método close():
void close()Um executor fechado não aceita novas tarefas. O método bloqueia até a conclusão de todas as tarefas já enviadas.
A maneira mais prática de garantir o encerramento é usar a construção try-with-resources, que chama close() automaticamente ao final do bloco:
try (ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor()) {
Future<Integer> f = executor.submit(() -> 42);
}
// executor.close() é chamado automaticamente aquiExemplo com ExecutorService e threads virtuais
import java.util.concurrent.*;
class Program {
public static void main(String[] args) {
System.out.println("Main thread started...");
try (ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor()) {
// executa 5 tarefas em threads virtuais
for (int i = 0; i < 5; i++) {
int taskNumber = i;
executor.submit(() -> {
try {
Thread.sleep(1000);
} catch (Exception ex) {
System.out.println(ex.getMessage());
}
System.out.println("Executing task " + taskNumber + " in " + Thread.currentThread());
});
}
}
System.out.println("Main thread finished...");
}
}Nesse exemplo, o ExecutorService cria uma nova thread virtual para cada tarefa enviada. O método submit() recebe a tarefa como uma expressão lambda. Cada tarefa pausa por um segundo simulando um trabalho em execução.
Saída típica:
Main thread started... Executing task 2 in VirtualThread[#39]/runnable@ForkJoinPool-1-worker-2 Executing task 3 in VirtualThread[#42]/runnable@ForkJoinPool-1-worker-5 Executing task 1 in VirtualThread[#37]/runnable@ForkJoinPool-1-worker-1 Executing task 0 in VirtualThread[#35]/runnable@ForkJoinPool-1-worker-4 Executing task 4 in VirtualThread[#46]/runnable@ForkJoinPool-1-worker-3 Main thread finished...
Cada tarefa é executada de forma independente, demonstrando a eficiência dos executores no gerenciamento de várias operações simultâneas.
Resumo
Executordefine a estrutura básica para executar tarefas.ExecutorServiceestende oExecutor, adicionando controle e monitoramento de tarefas assíncronas.- A classe
Executorsfornece métodos prontos para criar diferentes tipos de executores e pools de threads. - O método
submit()envia tarefasRunnableouCallablee retorna umFuturepara acompanhamento. - O método
close()encerra o executor e libera os recursos. - Threads virtuais oferecem uma forma moderna e eficiente de executar muitas tarefas concorrentes.