Concurrence et multithreading en Java : mécanismes avancés

Formateur Python en ligne pour débutants

Apprenez Python facilement sans surcharge théorique. Résolvez des tâches pratiques avec vérification automatique et écrivez du code directement dans le navigateur.

Start Course

Introduction : des synchronized de base aux outils modernes



Java est l'un des rares langages à offrir un support intégré du multithreading depuis sa création. Cependant, au niveau débutant, les développeurs se limitent souvent au mot-clé synchronized et aux simples Thread. Dans les systèmes réels à forte charge, cela ne suffit pas : les verrous génèrent des deadlocks, et la création naïve de threads entraîne une surcharge de la JVM.



Dans cet article, nous allons explorer les mécanismes avancés de concurrence en Java qui permettent d'écrire un code multithread efficace, scalable et sûr. Ce contenu s'adresse aux développeurs déjà familiers avec les bases du multithreading (moniteurs, wait/notify, volatile).



Nous aborderons quatre sujets clés : le Framework Fork/Join pour les tâches récursives, CompletableFuture pour la programmation asynchrone, StampedLock comme alternative à ReadWriteLock et l'utilisation judicieuse de ThreadLocal.






1. Framework Fork/Join : division récursive des tâches



Le Framework Fork/Join est apparu dans Java 7 et est conçu pour utiliser efficacement les processeurs multicœurs lors de la résolution de tâches pouvant être divisées en sous-tâches (diviser pour régner). La classe principale est ForkJoinPool, qui utilise le mécanisme de « vol de tâches » (work-stealing).



1.1. Principe de fonctionnement

Au lieu de créer des threads manuellement, vous décrivez une tâche comme héritant de RecursiveTask (si elle a une valeur de retour) ou de RecursiveAction (sans retour). Ensuite, vous divisez récursivement la tâche en parties jusqu'à ce qu'elles soient suffisamment petites pour être exécutées directement.



1.2. Exemple : somme parallèle d'un tableau

import java.util.concurrent.RecursiveTask;import java.util.concurrent.ForkJoinPool;

public class SumTask extends RecursiveTask<Long> { private static final int THRESHOLD = 10_000; private final int[] array; private final int start, end;

public SumTask(int[] array, int start, int end) { this.array = array; this.start = start; this.end = end; }

@Override protected Long compute() { if (end - start <= THRESHOLD) { long sum = 0; for (int i = start; i < end; i++) { sum += array[i]; } return sum; } int mid = start + (end - start) / 2; SumTask left = new SumTask(array, start, mid); SumTask right = new SumTask(array, mid, end); left.fork(); // lance la partie gauche de manière asynchrone long rightResult = right.compute(); // calcule la partie droite dans le thread actuel long leftResult = left.join(); // attend le résultat de la partie gauche return leftResult + rightResult; }

public static void main(String[] args) { int[] data = new int[1_000_000]; for (int i = 0; i < data.length; i++) data[i] = i;

ForkJoinPool pool = ForkJoinPool.commonPool(); long result = pool.invoke(new SumTask(data, 0, data.length)); System.out.println("Somme : " + result); }}


Important : utilisez ForkJoinPool.commonPool() — il est déjà optimisé pour le nombre de cœurs. Évitez de créer vos propres pools sans nécessité.






2. CompletableFuture : asynchrone sans l'enfer des callbacks



Avant Java 8, la programmation asynchrone était possible via Future, mais il était synchrone (appel bloquant get()). CompletableFuture résout ce problème en fournissant une API fonctionnelle puissante pour la composition d'opérations asynchrones.



2.1. Scénarios de base

Vous pouvez créer des CompletableFuture, lancer des tâches de manière asynchrone et les chaîner :



import java.util.concurrent.CompletableFuture;

public cl

Blogs

Book Recommendations