Files d'attente de tâches en Java : ExecutorService et ThreadPoolExecutor
Dans les applications multithreadées en Java, la gestion des tâches et de leur exécution est l'une des tâches clés. Au lieu de créer un nouveau thread pour chaque tâche (ce qui est coûteux et inefficace), les développeurs utilisent des files d'attente de tâches (Task Queues) en combinaison avec des pools de threads. Cela permet de contrôler le nombre de threads simultanés, de les réutiliser et de gérer en douceur les pics de charge.
La base pour travailler avec les files d'attente de tâches en Java est le framework java.util.concurrent, introduit dans Java 5. Les principaux composants sont : ExecutorService, ThreadPoolExecutor et diverses implémentations de BlockingQueue.
1. ExecutorService — interface de haut niveau
ExecutorService est une interface qui fournit des méthodes pour l'exécution asynchrone de tâches. Elle masque les détails de la gestion des threads. La façon la plus courante d'obtenir une instance est d'utiliser les méthodes d'usine de la classe Executors :
ExecutorService executor = Executors.newFixedThreadPool(4); // pool de 4 threadsexecutor.submit(() -> { System.out.println("La tâche s'exécute dans le thread : " + Thread.currentThread().getName());});executor.shutdown(); // arrêt après l'exécution de toutes les tâchesCependant, les méthodes d'usine offrent des possibilités de configuration limitées. Pour un réglage fin, on utilise ThreadPoolExecutor directement.
2. ThreadPoolExecutor — pool de threads configurable
ThreadPoolExecutor est le cœur du framework. Il accepte une file d'attente de tâches, une politique de saturation, une durée de vie des threads et d'autres paramètres. Le constructeur complet :
public ThreadPoolExecutor( int corePoolSize, // nombre minimum de threads int maximumPoolSize, // nombre maximum de threads long keepAliveTime, // temps d'inactivité d'un thread avant sa suppression TimeUnit unit, // unité de temps BlockingQueue<Runnable> workQueue, // file d'attente des tâches RejectedExecutionHandler handler // politique en cas de saturation);Exemple de création d'un pool avec une file d'attente personnalisée :
BlockingQueue<Runnable> queue = new ArrayBlockingQueue<>(100);ThreadPoolExecutor executor = new ThreadPoolExecutor( 2, 4, 30, TimeUnit.SECONDS, queue, new ThreadPoolExecutor.AbortPolicy() // par défaut — lève une exception);executor.execute(() -> { // tâche lourde});executor.shutdown();3. BlockingQueue — base de la file d'attente de tâches
Le type de file d'attente détermine comment les tâches seront stockées et transmises aux threads. Les implémentations les plus courantes :
LinkedBlockingQueue— file d'attente illimitée basée sur une liste chaînée. Si la capacité n'est pas définie, peut entraîner un débordement de mémoire.ArrayBlockingQueue— file d'attente limitée basée sur un tableau. Permet de contrôler le nombre de tâches en attente.SynchronousQueue— file d'attente de capacité nulle. Chaque tâche est transmise directement à un thread, sans attente dans la file.PriorityBlockingQueue— file d'attente avec priorités. Les tâches sont triées par priorité (implémententComparable).
Exemple avec une file d'attente limitée et une politique de rejet :
BlockingQueue<Runnable> queue = new ArrayBlockingQueue<>(10);ThreadPoolExecutor executor = new ThreadPoolExecutor( 2, 5, 10, TimeUnit.SECONDS, queue, new ThreadPoolExecutor.CallerRunsPolicy() // la tâche s'exécute dans le thread appelant);for (int i = 0; i < 20; i++) { executor.submit(() -> { Thread.sleep(100); return Math.random(); });}executor.shutdown();4. Politiques de saturation (RejectedExecutionHandler)
Lorsque la file d'attente est pleine