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:

feito

Mas isso não é seguido pela saída que normalmente vemos no IntelliJ IDEA:

Processo finalizado com código de saída 0

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:

  1. 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"));
    }
    
  2. 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:

1
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:

  1. 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));
    }
    
  2. 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());
    }
    
  3. 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):

10.000 tarefas foram submetidas 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