Considere un programa simple:


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"));
}

Ejecutar el programa produce la salida de la consola que esperamos:

hecho

Pero a esto no le sigue el resultado que solemos ver en IntelliJ IDEA:

Proceso finalizado con código de salida 0

Normalmente lo vemos cuando finaliza un programa.

¿Por qué sucede eso?

La descripción del método newFixedThreadPool() nos dice que los subprocesos creados con un ExecutorService continúan existiendo hasta que se detienen explícitamente. Eso significa que debido a que pasamos una tarea a ExecutorService , se creó un subproceso para ejecutarla, y ese subproceso continúa existiendo incluso después de que se realiza la tarea.

Parando en ExecutorService

Como resultado, necesitamos "cerrar" (o detener) ExecutorService . Podemos hacer esto de dos maneras:

  1. void shutdown() : después de llamar a este método, ExecutorService deja de aceptar nuevos trabajos. Todas las tareas enviadas previamente a ExecutorService continuarán ejecutándose.

    
    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 intenta detener los trabajos que están actualmente activos. Las tareas que todavía están esperando su turno se descartan y se devuelven como una 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);
    }
    

Producción:

1
2
4
3
java.util.concurrent.FutureTask@1e80bfe8[No completada, tarea = java.util.concurrent.Executors$RunnableAdapter@4edde6e5[Tarea empaquetada = Test$$Lambda$16/0x0000000800b95040@70177ecd]]
java.util.concurrent .FutureTask@cc34f4d[No completada, tarea = java.util.concurrent.Executors$RunnableAdapter@66a29884[Tarea empaquetada = Test$$Lambda$16/0x0000000800b95040@4769b07b]]
java.util.concurrent.FutureTask@6f539caf[No completada, tarea = java.util.concurrent.Executors$RunnableAdapter@17a7cec2[Tarea ajustada = Test$$Lambda$16/0x0000000800b95040@65b3120a]]
5

Proceso finalizado con código de salida 0

La salida diferirá de una ejecución a otra. Hay 2 tipos de líneas en la salida:

  • Un número significa que ExecutorService logró procesar la tarea correspondiente, mostrando el número de la lista que usamos para crear tareas.

  • Los resultados de llamar al método toString() en un objeto FutureTask . Estos objetos son las tareas que se enviaron a ExecutorService pero que no se procesaron.

La salida tiene otro matiz interesante. En un mundo ideal, primero veríamos todos los números mostrados, seguidos de los objetos FutureTask . Pero los problemas de sincronización mezclan las líneas en la salida.

Otros metodos

ExecutorService tiene varios métodos más relacionados con detenerlo:

  1. awaitTermination booleano (tiempo de espera prolongado, unidad de unidad de tiempo) : este método bloquea el subproceso que lo llama. El bloque termina tan pronto como ocurre cualquiera de los siguientes tres eventos:

    • después de llamar al método shutdown() , se han ejecutado todos los trabajos activos y todas las tareas programadas;
    • ha transcurrido el tiempo de espera determinado por los parámetros del método;
    • el subproceso que llamó al método awaitTermination() finaliza.

    El método devuelve verdadero si ExecutorService se detiene antes de que transcurra el tiempo de espera y falso si el tiempo de espera ya había transcurrido.

    
    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() : devuelve verdadero si se ha llamado al método shutdown() o shutdownNow() en 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 isTerpressed() : devuelve verdadero si se ha llamado al método shutdown() o shutdownNow() en ExecutorService y se han realizado todas las tareas.

    
    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 ejemplo que utiliza estos 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());
}

Salida (difiere de una ejecución a otra):

Se enviaron 10.000 tareas para su ejecución.
9170 tareas no se iniciaron.
Total de tareas completadas: 830 tareas.

Proceso finalizado con código de salida 0