Contenu du cours
Multithreading en Java
Multithreading en Java
Exécuteurs et Pool de Threads
Nous avons déjà exploré une variété de mécanismes pour prendre en charge le multithreading, et Executors
en fait partie !
Qu'est-ce que les Executors et le Pool de Threads ?
Executors
est un mécanisme qui offre des abstractions de haut niveau pour gérer les threads. Il vous permet de créer et de gérer un pool de threads, qui se compose 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 distribuée parmi les threads.
Alors, qu'est-ce qu'un pool de threads exactement ? C'est une collection de threads préexistants qui sont prêts à exécuter des tâches. En utilisant un pool de threads, vous évitez le surcoût de créer et détruire des threads de manière répétée, car les mêmes threads peuvent être réutilisés pour plusieurs tâches.
Remarque
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 traitée par un thread disponible du pool, et une fois la tâche terminée, le thread prend une nouvelle tâche de 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
Pensez à un restaurant où les 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 gèrent les commandes au fur et à mesure qu'elles arrivent. Une fois qu'un cuisinier a terminé une commande, il prend la suivante, ce qui aide à 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
ExecutorService executorService = Executors.newFixedThreadPool(20);
newCachedThreadPool()
: Crée un pool qui peut créer de nouveaux threads selon les besoins, mais réutilisera les threads disponibles s'il y en a.
Main
ExecutorService executorService = Executors.newCachedThreadPool();
newSingleThreadExecutor()
: Crée un pool de threads unique qui garantit que les tâches sont exécutées séquentiellement, c'est-à-dire l'une après l'autre. Ceci est utile pour les tâches qui doivent être exécutées dans un ordre strict.
Main
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. Il 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
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()
démarre un arrêt en douceur du pool de threads. Elle cesse d'accepter de nouvelles tâches mais terminera 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 soient terminées dans le délai imparti. C'est une attente bloquante qui vous permet de vous 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 aide à suivre l'état du thread, c'est l'interface Future
. La méthode submit()
de l'interface ExecutorService
renvoie une implémentation de l'interface Future
.
Si vous souhaitez obtenir le résultat de l'exécution du thread, vous pouvez utiliser la méthode get()
, si le thread implémente Runnable
, la méthode get()
ne renvoie rien, mais si Callable<T>
, elle renvoie le type T
.
Main
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(); } }
Vous pouvez également 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 drapeau 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 d'exécution.
Bien et 2 méthodes qui comprennent intuitivement ce qu'elles font :
isCancelled()
: Vérifie si la tâche a été annulée;isDone()
: Vérifie si la tâche a été complétée.
Exemple d'utilisation
Il est optimal d'utiliser le nombre de threads = processor cores
. Vous pouvez le voir dans le code en utilisant Runtime.getRuntime().availableProcessors()
.
Main
int availableProcessors = Runtime.getRuntime().availableProcessors();
Différences entre la création directe de Threads et l'utilisation de ExecutorService
Les principales différences entre la création directe de threads et l'utilisation de ExecutorService
sont la commodité et la gestion des ressources. La création manuelle de threads nécessite de gérer chaque thread individuellement, ce qui complique le code et l'administration.
ExecutorService
simplifie la gestion en utilisant un pool de threads, facilitant ainsi la gestion des tâches. De plus, alors que la création manuelle de threads peut entraîner une consommation élevée de ressources, ExecutorService
vous permet de personnaliser la taille du pool de threads.
Merci pour vos commentaires !