Considere um programa simples:
public static void main(String[] args) throws Exception {
// Create an ExecutorService with a fixed number of threads: three
ExecutorService service = Executors.newFixedThreadPool(3);
// Pass a simple Runnable task to the ExecutorService
service.submit(() -> System.out.println("done"));
}
A execução do programa produz a saída do console que esperamos:
Mas isso não é seguido pela saída que normalmente vemos no IntelliJ IDEA:
Geralmente vemos isso quando um programa termina.
Por que isso acontece?
A descrição do método newFixedThreadPool() nos informa que os encadeamentos criados usando um ExecutorService continuam a existir até que sejam interrompidos explicitamente. Isso significa que, como passamos uma tarefa para o ExecutorService , um thread foi criado para executá-la e esse thread continua a existir mesmo após a conclusão da tarefa.
Parando no ExecutorService
Como resultado, precisamos "desligar" (ou parar) o ExecutorService . Podemos fazer isso de duas maneiras:
-
void shutdown() — depois que esse método é chamado, o ExecutorService para de aceitar novos trabalhos. Todas as tarefas enviadas anteriormente ao ExecutorService continuarão em execução.
public static void main(String[] args) throws Exception { ExecutorService service = Executors.newFixedThreadPool(3); service.submit(() -> System.out.println("task 1")); service.submit(() -> System.out.println("task 2")); service.shutdown(); // A RejectedExecutionException will occur here service.submit(() -> System.out.println("task 3")); }
-
List<Runnable> shutdownNow() — Este método tenta interromper os trabalhos que estão ativos no momento. As tarefas que ainda estão aguardando sua vez são descartadas e retornadas como uma lista de Runnables .
public static void main(String[] args) throws Exception { ExecutorService service = Executors.newFixedThreadPool(5); List.of(1, 2, 3, 4, 5, 6, 7, 8).forEach(i -> service.submit(() -> System.out.println(i))); List<Runnable> runnables = service.shutdownNow(); runnables.forEach(System.out::println); }
Saída:
2
4
3
java.util.concurrent.FutureTask@1e80bfe8[Não concluído, tarefa = java.util.concurrent.Executors$RunnableAdapter@4edde6e5[Tarefa agrupada = Test$$Lambda$16/0x0000000800b95040@70177ecd]]
java.util.concurrent .FutureTask@cc34f4d[Não concluída, tarefa = java.util.concurrent.Executors$RunnableAdapter@66a29884[Tarefa agrupada = Test$$Lambda$16/0x0000000800b95040@4769b07b]]
java.util.concurrent.FutureTask@6f539caf[Não concluída, tarefa = java.util.concurrent.Executors$RunnableAdapter@17a7cec2[Tarefa encapsulada = Test$$Lambda$16/0x0000000800b95040@65b3120a]]
5
Processo concluído com código de saída 0
A saída será diferente de execução para execução. Existem 2 tipos de linhas na saída:
-
Um número significa que o ExecutorService conseguiu processar a tarefa correspondente, exibindo o número da lista que usamos para criar tarefas.
-
Os resultados da chamada do método toString() em um objeto FutureTask . Esses objetos são as tarefas que foram enviadas ao ExecutorService , mas não foram processadas.
A saída tem outra nuance interessante. Em um mundo ideal, veríamos primeiro todos os números exibidos, seguidos pelos objetos FutureTask . Mas os problemas de sincronização confundem as linhas na saída.
Outros métodos
ExecutorService tem vários outros métodos relacionados a interrompê-lo:
-
boolean awaitTermination(long timeout, TimeUnit unit) — este método bloqueia o thread que o chama. O bloco termina assim que qualquer um dos três eventos a seguir ocorrer:
- depois que o método shutdown() é chamado, todos os trabalhos ativos e todas as tarefas agendadas foram executadas;
- o timeout determinado pelos parâmetros do método expirou;
- o thread que chamou o método awaitTermination() é finalizado.
O método retorna true se o ExecutorService for interrompido antes do tempo limite expirar e false se o tempo limite já tiver expirado.
public static void main(String[] args) throws Exception { ExecutorService service = Executors.newFixedThreadPool(2); service.submit(() -> System.out.println("task 1")); service.submit(() -> System.out.println("task 2")); service.submit(() -> System.out.println("task 3")); service.shutdown(); System.out.println(service.awaitTermination(1, TimeUnit.MICROSECONDS)); }
-
boolean isShutdown() — Retorna true se o método shutdown() ou shutdownNow() foi chamado no ExecutorService .
public static void main(String[] args) throws Exception { ExecutorService service = Executors.newFixedThreadPool(2); service.submit(() -> System.out.println("task 1")); service.submit(() -> System.out.println("task 2")); service.submit(() -> System.out.println("task 3")); System.out.println(service.isShutdown()); service.shutdown(); System.out.println(service.isShutdown()); }
-
boolean isTerminated() — Retorna true se o método shutdown() ou shutdownNow() foi chamado no ExecutorService e todas as tarefas foram concluídas.
public static void main(String[] args) throws Exception { ExecutorService service = Executors.newFixedThreadPool(5); List.of(1, 2, 3, 4, 5, 6, 7, 8).forEach(i -> service.submit(() -> System.out.println(i))); service.shutdownNow(); System.out.println(service.isTerminated()); }
Código de exemplo que usa estes métodos:
public static void main(String[] args) throws Exception {
ExecutorService service = Executors.newFixedThreadPool(16);
Callable<String> task = () -> {
Thread.sleep(1);
return "Done";
};
// Add 10,000 tasks to the queue
List<Future<String>> futures = IntStream.range(0, 10_000)
.mapToObj(i -> service.submit(task))
.collect(Collectors.toList());
System.out.printf("%d tasks were submitted for execution.%n", futures.size());
// Attempt to shut down
service.shutdown();
// Wait 100 milliseconds to finish the work
if (service.awaitTermination(100, TimeUnit.MILLISECONDS)) {
System.out.println("All tasks completed!");
} else {
// Stop forcibly
List<Runnable> notExecuted = service.shutdownNow();
System.out.printf("%d tasks were not started.%n", notExecuted.size());
}
System.out.printf("Total tasks completed: %d.%n", futures.stream().filter(Future::isDone).count());
}
Saída (difere de execução para execução):
9170 tarefas não foram iniciadas.
Total de tarefas concluídas: 830 tarefas.
Processo finalizado com código de saída 0
GO TO FULL VERSION