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:

  • todas as tarefas agendadas estão concluídas;
  • o timeout passado para o método expirou;
  • o segmento atual é interrompido.

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
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).