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

L'exécution du programme produit la sortie de console que nous attendons :

fait

Mais cela n'est pas suivi de la sortie que nous voyons habituellement dans IntelliJ IDEA :

Processus terminé avec le code de sortie 0

Nous voyons généralement cela lorsqu'un programme se termine.

Pourquoi cela arrive-t-il ?

La description de la méthode newFixedThreadPool() nous indique que les threads créés à l'aide d'un ExecutorService continuent d'exister jusqu'à ce qu'ils soient explicitement arrêtés. Cela signifie que parce que nous avons passé une tâche à ExecutorService , un thread a été créé pour l'exécuter, et ce thread continue d'exister même après la fin de la tâche.

Arrêt à ExecutorService

En conséquence, nous devons "fermer" (ou arrêter) ExecutorService . Nous pouvons le faire de deux manières :

  1. void shutdown() — après l'appel de cette méthode, ExecutorService arrête d'accepter de nouvelles tâches. Toutes les tâches précédemment soumises à ExecutorService continueront à s'exécuter.

    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() — Cette méthode tente d'arrêter les tâches actuellement actives. Les tâches qui attendent toujours leur tour sont ignorées et renvoyées sous la forme d'une liste 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);
    }

Sortir:

1
2
4
3
_
_ .FutureTask@cc34f4d[Non terminé, tâche = java.util.concurrent.Executors$RunnableAdapter@66a29884[Tâche enveloppée = Test$$Lambda$16/0x0000000800b95040@4769b07b]]
java.util.concurrent.FutureTask@6f539caf[Non terminé, tâche = java.util.concurrent.Executors$RunnableAdapter@17a7cec2[Tâche enveloppée = Test$$Lambda$16/0x0000000800b95040@65b3120a]]
5

Processus terminé avec le code de sortie 0

La sortie sera différente d'une exécution à l'autre. Il existe 2 types de lignes dans la sortie :

  • Un nombre signifie que l' ExecutorService a réussi à traiter la tâche correspondante, en affichant le numéro de la liste que nous avons utilisée pour créer des tâches.

  • Les résultats de l'appel de la méthode toString() sur un objet FutureTask . Ces objets sont les tâches qui ont été soumises à ExecutorService mais qui n'ont pas été traitées.

La sortie a une autre nuance intéressante. Dans un monde idéal, nous verrions d'abord tous les nombres affichés, suivis des objets FutureTask . Mais les problèmes de synchronisation brouillent les lignes dans la sortie.

Autres méthodes

ExecutorService a plusieurs autres méthodes liées à son arrêt :

  1. boolean awaitTermination(long timeout, unité TimeUnit) — cette méthode bloque le thread qui l'appelle. Le blocage se termine dès que l'un des trois événements suivants se produit :

    • après l'appel de la méthode shutdown() , tous les travaux actifs et toutes les tâches planifiées ont été exécutés ;
    • le délai d'attente déterminé par les paramètres de la méthode s'est écoulé ;
    • le thread qui a appelé la méthode awaitTermination() est terminé.

    La méthode renvoie true si ExecutorService est arrêté avant l'expiration du délai d'attente et false si le délai d'attente s'est déjà écoulé.

    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() — Renvoie true si la méthode shutdown() ou shutdownNow() a été appelée sur 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() — Renvoie true si la méthode shutdown() ou shutdownNow() a été appelée sur ExecutorService et que toutes les tâches sont terminées.

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

Exemple de code qui utilise ces méthodes :

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

Sortie (diffère d'une exécution à l'autre) :

10 000 tâches ont été soumises pour exécution.
9170 tâches n'ont pas été démarrées.
Total des tâches terminées : 830 tâches.

Processus terminé avec le code de sortie 0