Notice: This page requires JavaScript to function properly.
Please enable JavaScript in your browser settings or update your browser.
Apprendre Exécuteurs et Pool de Threads | Mécanismes de Synchronisation de Haut Niveau
Multithreading en Java

bookExécuteurs et Pool de Threads

Nous avons déjà exploré une variété de mécanismes pour prendre en charge le multithreading, et les Executors en font partie !

Que sont les Executors et le Thread Pooling ?

Les Executors constituent un mécanisme offrant des abstractions de haut niveau pour la gestion des threads. Ils permettent de créer et de gérer un pool de threads, composé d'un ensemble de threads préexistants prêts à exécuter des tâches. Au lieu de créer un nouveau thread pour chaque tâche, les tâches sont envoyées au pool, où leur exécution est répartie entre les threads.

Mais qu'est-ce qu'un thread pool exactement ? Il s'agit d'un ensemble de threads préexistants prêts à exécuter des tâches. L'utilisation d'un thread pool permet d'éviter la surcharge liée à la création et à la destruction répétées de threads, car les mêmes threads peuvent être réutilisés pour plusieurs tâches.

Note
Note

S'il y a plus de tâches que de threads, les tâches attendent dans la Task Queue. Une tâche de la file d'attente est prise en charge par un thread disponible du pool, et une fois la tâche terminée, le thread prend une nouvelle tâche dans la file d'attente. Une fois que toutes les tâches de la file d'attente sont terminées, les threads restent actifs et attendent de nouvelles tâches.

Exemple de la vie courante

Imaginez un restaurant où des cuisiniers (threads) préparent des commandes (tâches). Au lieu d'embaucher un nouveau cuisinier pour chaque commande, le restaurant emploie un nombre limité de cuisiniers qui traitent les commandes à mesure qu'elles arrivent. Lorsqu'un cuisinier termine une commande, il prend la suivante, ce qui permet d'utiliser efficacement les ressources du restaurant.

Méthode principale

newFixedThreadPool(int n) : Crée un pool avec un nombre fixe de threads égal à n.

Main.java

Main.java

copy
1
ExecutorService executorService = Executors.newFixedThreadPool(20);

newCachedThreadPool() : Crée un pool pouvant créer de nouveaux threads selon les besoins, mais réutilisera les threads disponibles s'il y en a.

Main.java

Main.java

copy
1
ExecutorService executorService = Executors.newCachedThreadPool();

newSingleThreadExecutor(): Crée un pool à un seul thread qui garantit que les tâches sont exécutées séquentiellement, c'est-à-dire l'une après l'autre. Utile pour les tâches devant être exécutées dans un ordre strict.

Main.java

Main.java

copy
1
ExecutorService executorService = Executors.newSingleThreadExecutor();

Dans tous les exemples, les méthodes de Executors renvoient une implémentation de l'interface ExecutorService, qui est utilisée pour gérer les threads.

ExecutorService fournit des méthodes pour gérer un pool de threads. Par exemple, submit(Runnable task) accepte une tâche sous forme d'objet Runnable et la place dans une file d'attente pour exécution. Elle renvoie un objet Future, qui peut être utilisé pour vérifier l'état de la tâche et obtenir un résultat si la tâche produit un résultat.

Main.java

Main.java

copy
12345678910111213141516171819202122232425262728293031323334
package com.example; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; public class Main { public static void main(String[] args) { // Create a thread pool with 5 threads ExecutorService executor = Executors.newFixedThreadPool(5); // Define the task to be executed Runnable task = () -> { System.out.println("Task is running: " + Thread.currentThread().getName()); try { Thread.sleep(2000); // Simulate some work } catch (InterruptedException e) { Thread.currentThread().interrupt(); } System.out.println("Task completed: " + Thread.currentThread().getName()); }; // Submit the task for execution and get a `Future` Future<?> future = executor.submit(task); // Check if the task is done System.out.println("Is task done? " + future.isDone()); // You can use `future` to check the status of the task or wait for its completion // Example: future.get() - blocks until the task is completed (not used in this example) // Initiate an orderly shutdown of the executor service executor.shutdown(); } }

La méthode shutdown() lance un arrêt progressif du pool de threads. Elle n'accepte plus de nouvelles tâches mais termine les tâches en cours. Une fois que vous appelez cette méthode, le pool ne peut pas être redémarré.

La méthode awaitTermination(long timeout, TimeUnit unit) attend que toutes les tâches du pool se terminent dans le délai imparti. Il s'agit d'une attente bloquante qui permet de s'assurer que toutes les tâches sont terminées avant de finaliser le pool.

Nous n'avons pas non plus mentionné la principale interface qui permet de suivre l'état du thread, il s'agit de l'interface Future. La méthode submit() de l'interface ExecutorService retourne une implémentation de l'interface Future.

Pour obtenir le résultat de l'exécution du thread, il est possible d'utiliser la méthode get(). Si le thread implémente Runnable, la méthode get() ne retourne rien, mais si c'est Callable<T>, elle retourne un type T.

Main.java

Main.java

copy
12345678910111213141516171819202122232425262728293031
package com.example; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; public class Main { public static void main(String[] args) { ExecutorService executor = Executors.newFixedThreadPool(3); // Callable task that returns a result Callable<String> task = () -> { Thread.sleep(1000); // Simulate some work return "Task result"; }; Future<String> future = executor.submit(task); try { // Get the result of the task String result = future.get(); System.out.println("Task completed with result: " + result); } catch (InterruptedException | ExecutionException e) { e.printStackTrace(); } executor.shutdown(); } }

Il est également possible d'utiliser la méthode cancel(boolean mayInterruptIfRunning) pour tenter d'annuler l'exécution d'une tâche. Si la tâche n'a pas encore commencé, elle sera annulée. Si la tâche est déjà en cours d'exécution, elle peut être interrompue en fonction du paramètre mayInterruptIfRunning.

true : Si la tâche est en cours d'exécution, elle sera interrompue en appelant Thread.interrupt() sur le thread en cours d'exécution. false : Si la tâche est en cours d'exécution, elle ne sera pas interrompue et la tentative d'annulation n'aura aucun effet sur la tâche en cours.

Ainsi que 2 méthodes dont la fonction est intuitive :

  • isCancelled(): Vérifie si la tâche a été annulée ;
  • isDone(): Vérifie si la tâche a été terminée.

Exemple d'utilisation

Note
Remarque

La taille du pool de threads dépend de la nature des tâches exécutées. En général, la taille du pool ne doit pas être codée en dur ; elle doit être personnalisable. La taille optimale est déterminée en surveillant le débit des tâches exécutées.

Il est optimal d'utiliser un nombre de threads = processor cores. Cela peut être observé dans le code en utilisant Runtime.getRuntime().availableProcessors().

Main.java

Main.java

copy
1
int availableProcessors = Runtime.getRuntime().availableProcessors();

Différences entre la création directe de threads et l'utilisation d'ExecutorService

Les principales différences entre la création de threads directement et l'utilisation de ExecutorService résident dans la commodité et la gestion des ressources. La création manuelle de threads nécessite de gérer chaque thread individuellement, ce qui complexifie le code et l'administration.

ExecutorService simplifie la gestion en utilisant un pool de threads, ce qui facilite la gestion des tâches. De plus, tandis que la création manuelle de threads peut entraîner une consommation élevée de ressources, ExecutorService permet de personnaliser la taille du pool de threads.

Tout était clair ?

Comment pouvons-nous l'améliorer ?

Merci pour vos commentaires !

Section 3. Chapitre 6

Demandez à l'IA

expand

Demandez à l'IA

ChatGPT

Posez n'importe quelle question ou essayez l'une des questions suggérées pour commencer notre discussion

Awesome!

Completion rate improved to 3.33

bookExécuteurs et Pool de Threads

Glissez pour afficher le menu

Nous avons déjà exploré une variété de mécanismes pour prendre en charge le multithreading, et les Executors en font partie !

Que sont les Executors et le Thread Pooling ?

Les Executors constituent un mécanisme offrant des abstractions de haut niveau pour la gestion des threads. Ils permettent de créer et de gérer un pool de threads, composé d'un ensemble de threads préexistants prêts à exécuter des tâches. Au lieu de créer un nouveau thread pour chaque tâche, les tâches sont envoyées au pool, où leur exécution est répartie entre les threads.

Mais qu'est-ce qu'un thread pool exactement ? Il s'agit d'un ensemble de threads préexistants prêts à exécuter des tâches. L'utilisation d'un thread pool permet d'éviter la surcharge liée à la création et à la destruction répétées de threads, car les mêmes threads peuvent être réutilisés pour plusieurs tâches.

Note
Note

S'il y a plus de tâches que de threads, les tâches attendent dans la Task Queue. Une tâche de la file d'attente est prise en charge par un thread disponible du pool, et une fois la tâche terminée, le thread prend une nouvelle tâche dans la file d'attente. Une fois que toutes les tâches de la file d'attente sont terminées, les threads restent actifs et attendent de nouvelles tâches.

Exemple de la vie courante

Imaginez un restaurant où des cuisiniers (threads) préparent des commandes (tâches). Au lieu d'embaucher un nouveau cuisinier pour chaque commande, le restaurant emploie un nombre limité de cuisiniers qui traitent les commandes à mesure qu'elles arrivent. Lorsqu'un cuisinier termine une commande, il prend la suivante, ce qui permet d'utiliser efficacement les ressources du restaurant.

Méthode principale

newFixedThreadPool(int n) : Crée un pool avec un nombre fixe de threads égal à n.

Main.java

Main.java

copy
1
ExecutorService executorService = Executors.newFixedThreadPool(20);

newCachedThreadPool() : Crée un pool pouvant créer de nouveaux threads selon les besoins, mais réutilisera les threads disponibles s'il y en a.

Main.java

Main.java

copy
1
ExecutorService executorService = Executors.newCachedThreadPool();

newSingleThreadExecutor(): Crée un pool à un seul thread qui garantit que les tâches sont exécutées séquentiellement, c'est-à-dire l'une après l'autre. Utile pour les tâches devant être exécutées dans un ordre strict.

Main.java

Main.java

copy
1
ExecutorService executorService = Executors.newSingleThreadExecutor();

Dans tous les exemples, les méthodes de Executors renvoient une implémentation de l'interface ExecutorService, qui est utilisée pour gérer les threads.

ExecutorService fournit des méthodes pour gérer un pool de threads. Par exemple, submit(Runnable task) accepte une tâche sous forme d'objet Runnable et la place dans une file d'attente pour exécution. Elle renvoie un objet Future, qui peut être utilisé pour vérifier l'état de la tâche et obtenir un résultat si la tâche produit un résultat.

Main.java

Main.java

copy
12345678910111213141516171819202122232425262728293031323334
package com.example; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; public class Main { public static void main(String[] args) { // Create a thread pool with 5 threads ExecutorService executor = Executors.newFixedThreadPool(5); // Define the task to be executed Runnable task = () -> { System.out.println("Task is running: " + Thread.currentThread().getName()); try { Thread.sleep(2000); // Simulate some work } catch (InterruptedException e) { Thread.currentThread().interrupt(); } System.out.println("Task completed: " + Thread.currentThread().getName()); }; // Submit the task for execution and get a `Future` Future<?> future = executor.submit(task); // Check if the task is done System.out.println("Is task done? " + future.isDone()); // You can use `future` to check the status of the task or wait for its completion // Example: future.get() - blocks until the task is completed (not used in this example) // Initiate an orderly shutdown of the executor service executor.shutdown(); } }

La méthode shutdown() lance un arrêt progressif du pool de threads. Elle n'accepte plus de nouvelles tâches mais termine les tâches en cours. Une fois que vous appelez cette méthode, le pool ne peut pas être redémarré.

La méthode awaitTermination(long timeout, TimeUnit unit) attend que toutes les tâches du pool se terminent dans le délai imparti. Il s'agit d'une attente bloquante qui permet de s'assurer que toutes les tâches sont terminées avant de finaliser le pool.

Nous n'avons pas non plus mentionné la principale interface qui permet de suivre l'état du thread, il s'agit de l'interface Future. La méthode submit() de l'interface ExecutorService retourne une implémentation de l'interface Future.

Pour obtenir le résultat de l'exécution du thread, il est possible d'utiliser la méthode get(). Si le thread implémente Runnable, la méthode get() ne retourne rien, mais si c'est Callable<T>, elle retourne un type T.

Main.java

Main.java

copy
12345678910111213141516171819202122232425262728293031
package com.example; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; public class Main { public static void main(String[] args) { ExecutorService executor = Executors.newFixedThreadPool(3); // Callable task that returns a result Callable<String> task = () -> { Thread.sleep(1000); // Simulate some work return "Task result"; }; Future<String> future = executor.submit(task); try { // Get the result of the task String result = future.get(); System.out.println("Task completed with result: " + result); } catch (InterruptedException | ExecutionException e) { e.printStackTrace(); } executor.shutdown(); } }

Il est également possible d'utiliser la méthode cancel(boolean mayInterruptIfRunning) pour tenter d'annuler l'exécution d'une tâche. Si la tâche n'a pas encore commencé, elle sera annulée. Si la tâche est déjà en cours d'exécution, elle peut être interrompue en fonction du paramètre mayInterruptIfRunning.

true : Si la tâche est en cours d'exécution, elle sera interrompue en appelant Thread.interrupt() sur le thread en cours d'exécution. false : Si la tâche est en cours d'exécution, elle ne sera pas interrompue et la tentative d'annulation n'aura aucun effet sur la tâche en cours.

Ainsi que 2 méthodes dont la fonction est intuitive :

  • isCancelled(): Vérifie si la tâche a été annulée ;
  • isDone(): Vérifie si la tâche a été terminée.

Exemple d'utilisation

Note
Remarque

La taille du pool de threads dépend de la nature des tâches exécutées. En général, la taille du pool ne doit pas être codée en dur ; elle doit être personnalisable. La taille optimale est déterminée en surveillant le débit des tâches exécutées.

Il est optimal d'utiliser un nombre de threads = processor cores. Cela peut être observé dans le code en utilisant Runtime.getRuntime().availableProcessors().

Main.java

Main.java

copy
1
int availableProcessors = Runtime.getRuntime().availableProcessors();

Différences entre la création directe de threads et l'utilisation d'ExecutorService

Les principales différences entre la création de threads directement et l'utilisation de ExecutorService résident dans la commodité et la gestion des ressources. La création manuelle de threads nécessite de gérer chaque thread individuellement, ce qui complexifie le code et l'administration.

ExecutorService simplifie la gestion en utilisant un pool de threads, ce qui facilite la gestion des tâches. De plus, tandis que la création manuelle de threads peut entraîner une consommation élevée de ressources, ExecutorService permet de personnaliser la taille du pool de threads.

Tout était clair ?

Comment pouvons-nous l'améliorer ?

Merci pour vos commentaires !

Section 3. Chapitre 6
some-alt