Por que você precisa da interface do Executor?
Antes do Java 5, você tinha que escrever todo o seu próprio gerenciamento de encadeamento de código em seu aplicativo. Além disso, criar umnovo tópicoO objeto é uma operação com uso intensivo de recursos e não faz sentido criar um novo thread para cada tarefa leve. E como esse problema é familiar para absolutamente todos os desenvolvedores de aplicativos multiencadeados, eles decidiram trazer essa funcionalidade para o Java como a estrutura Executor .
Qual é a grande idéia? É simples: ao invés de criar uma nova thread para cada nova tarefa, as threads são mantidas em uma espécie de "armazenamento", e quando chega uma nova tarefa, recuperamos uma thread existente ao invés de criar uma nova.
As principais interfaces dessa estrutura são Executor , ExecutorService e ScheduledExecutorService , cada uma das quais estende a funcionalidade da anterior.

A interface Executor é a interface base. Ele declara um único método void execute(Runnable command) que é implementado por um objeto Runnable .
A interface ExecutorService é mais interessante. Possui métodos para gerenciar a conclusão do trabalho, bem como métodos para retornar algum tipo de resultado. Vamos dar uma olhada em seus métodos:
Método | Descrição |
---|---|
void desligamento(); | Chamar esse método interrompe o ExecutorService . Todas as tarefas que já foram enviadas para processamento serão concluídas, mas novas tarefas não serão aceitas. |
List<Executável> shutdownNow(); | Chamar esse método interrompe o ExecutorService . Thread.interrupt será chamado para todas as tarefas que já foram enviadas para processamento. Este método retorna uma lista de tarefas enfileiradas. O método não espera a conclusão de todas as tarefas que estão "em andamento" no momento em que o método é chamado. Aviso: chamar esse método pode vazar recursos. |
booleano isShutdown(); | Verifica se o ExecutorService está parado. |
boolean isTerminated(); | Retorna true se todas as tarefas foram concluídas após o desligamento do ExecutorService . Até que shutdown() ou shutdownNow() seja chamado, ele sempre retornará false . |
boolean awaitTermination (long timeout, TimeUnit unit) gera InterruptedException; | Depois que o método shutdown() é chamado, esse método bloqueia o thread no qual é chamado, até que uma das seguintes condições seja verdadeira:
Retorna verdadeiro se todas as tarefas forem concluídas e falso se o tempo limite expirar antes do término. |
<T> Future<T> submit(Tarefa Callable<T>); | Adiciona uma tarefa Callable ao ExecutorService e retorna um objeto que implementa a interface Future . <T> é o tipo do resultado da tarefa passada. |
<T> Future<T> submit(Tarefa Executável, T resultado); | Adiciona uma tarefa Runnable ao ExecutorService e retorna um objeto que implementa a interface Future . O parâmetro de resultado T é o que é retornado por uma chamada para o método get() no resultadoObjeto futuro. |
Future<?> submit(tarefa executável); | Adiciona uma tarefa Runnable ao ExecutorService e retorna um objeto que implementa a interface Future . Se chamarmos o método get() no objeto Future resultante , obteremos null. |
<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks) lança InterruptedException; | Passa uma lista de tarefas Callable para o ExecutorService . Retorna uma lista de Futuros da qual podemos obter o resultado do trabalho. Essa lista é retornada quando todas as tarefas enviadas são concluídas. Se a coleção de tarefas for modificada durante a execução do método, o resultado desse método será indefinido. |
<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit) throws InterruptedException; | Passa uma lista de tarefas Callable para o ExecutorService . Retorna uma lista de Futuros da qual podemos obter o resultado do trabalho. Essa lista é retornada quando todas as tarefas passadas são concluídas ou após o tempo limite passado para o método ter decorrido, o que ocorrer primeiro. Se o tempo limite expirar, as tarefas inacabadas serão canceladas. Observação: é possível que uma tarefa cancelada não pare de ser executada (veremos esse efeito colateral no exemplo). Se a coleção de tarefas for modificada durante a execução do método, o resultado desse método será indefinido. |
<T> T invokeAny(Collection<? extends Callable<T>> tasks) lança InterruptedException, ExecutionException; | Passa uma lista de tarefas Callable para o ExecutorService . Retorna o resultado de uma das tarefas (se houver) concluída sem gerar uma exceção (se houver). Se a coleção de tarefas for modificada durante a execução do método, o resultado desse método será indefinido. |
<T> T invokeAny(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit) lança InterruptedException, ExecutionException, TimeoutException; | Passa uma lista de tarefas Callable para o ExecutorService . Retorna o resultado de uma das tarefas (se houver) concluída sem lançar uma exceção antes que o tempo limite passado para o método tenha decorrido. Se a coleção de tarefas for modificada durante a execução do método, o resultado desse método será indefinido. |
Vejamos um pequeno exemplo de trabalho com ExecutorService .
import java.util.List;
import java.util.concurrent.*;
public class ExecutorServiceTest {
public static void main(String[] args) throws InterruptedException, ExecutionException, TimeoutException {
//Create an ExecutorService for 2 threads
java.util.concurrent.ExecutorService executorService = new ThreadPoolExecutor(2, 2, 60, TimeUnit.SECONDS, new ArrayBlockingQueue<>(10));
// Create 5 tasks
MyRunnable task1 = new MyRunnable();
MyRunnable task2 = new MyRunnable();
MyRunnable task3 = new MyRunnable();
MyRunnable task4 = new MyRunnable();
MyRunnable task5 = new MyRunnable();
final List<MyRunnable> tasks = List.of(task1, task2, task3, task4, task5);
// Pass a list that contains the 5 tasks we created
final List<Future<Void>> futures = executorService.invokeAll(tasks, 6, TimeUnit.SECONDS);
System.out.println("Futures received");
// Stop the ExecutorService
executorService.shutdown();
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(executorService.isShutdown());
System.out.println(executorService.isTerminated());
}
public static class MyRunnable implements Callable<Void> {
@Override
public void call() {
// Add 2 delays. When the ExecutorService is stopped, we will see which delay is in progress when the attempt is made to stop execution of the task
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
System.out.println("sleep 1: " + e.getMessage());
}
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
System.out.println("sleep 2: " + e.getMessage());
}
System.out.println("done");
return null;
}
}
}
Saída:
feito
Futuros recebidos
sono 1: sono interrompido
sono 1: sono interrompido
feito
feito
verdadeiro
verdadeiro
Cada tarefa é executada por 5 segundos. Criamos um pool para dois threads, então as duas primeiras linhas de saída fazem todo o sentido.
Seis segundos após o início do programa, o método invokeAll atinge o tempo limite e o resultado é retornado como uma lista de Futures . Isso pode ser visto na string de saída Futuros recebidos .
Depois que as duas primeiras tarefas são concluídas, outras duas começam. Mas como o tempo limite definido no método invokeAll expira, essas duas tarefas não têm tempo para serem concluídas. Eles recebem um comando "cancelar" . É por isso que a saída tem duas linhas com sono 1: sono interrompido .
E então você pode ver mais duas linhas com done . Este é o efeito colateral que mencionei ao descrever o método invokeAll .
A quinta e última tarefa nunca é iniciada, então não vemos nada sobre ela na saída.
As duas últimas linhas são o resultado da chamada dos métodos isShutdown e isTerminated .
Também é interessante executar este exemplo no modo de depuração e observar o status da tarefa após o tempo limite (defina um ponto de interrupção na linha com executorService.shutdown(); ):

Vemos que duas tarefas foram concluídas normalmente e três tarefas foram "canceladas" .
ScheduledExecutorService
Para concluir nossa discussão sobre executores, vamos dar uma olhada em ScheduledExecutorService .
Possui 4 métodos:
Método | Descrição |
---|---|
public ScheduledFuture<?> schedule(comando executável, longo atraso, unidade TimeUnit); | Agenda a tarefa Runnable aprovada para ser executada uma vez após o atraso especificado como um argumento. |
public <V> ScheduledFuture<V> schedule(Callable<V> callable, long delay, TimeUnit unit); | Agenda a tarefa Callable aprovada para ser executada uma vez após o atraso especificado como um argumento. |
public ScheduledFuture<?> scheduleAtFixedRate(comando executável, atraso inicial longo, período longo, unidade TimeUnit); | Agenda a execução periódica da tarefa aprovada, que será executada pela primeira vez após initialDelay , e cada execução subsequente começará após period . |
public ScheduledFuture<?> scheduleWithFixedDelay(comando executável, atraso inicial longo, atraso longo, unidade TimeUnit); | Agenda a execução periódica da tarefa aprovada, que será executada pela primeira vez após initialDelay , e cada execução subsequente começará após o atraso (o período entre a conclusão da execução anterior e o início da atual). |
GO TO FULL VERSION