Concurrency & Multithreading en Java
Guide Complet: Des Fondamentaux aux Concepts Avancés
Basé sur la Roadmap Java Multithreading
Dr. BADR EL KHALYLY
Table des matières
1 Fondamentaux : Programme, Processus, Thread et Tâche 4
1.1 Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
1.2 Programme . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
1.3 Processus . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
1.4 Thread . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
1.4.1 La classe [Link] . . . . . . . . . . . . . . . . . . . . . . 6
1.4.2 Les 6 états d'un Thread ([Link]) . . . . . . . . . . . . . . . 7
1.4.3 Méthodes principales de la classe Thread . . . . . . . . . . . . . . . 7
1.4.4 Ambiguïtés et pièges fréquents . . . . . . . . . . . . . . . . . . . . . 8
1.5 Tâche (Task) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
1.5.1 L'interface Runnable . . . . . . . . . . . . . . . . . . . . . . . . . . 9
1.5.2 L'interface Callable<V> . . . . . . . . . . . . . . . . . . . . . . . . 10
1.5.3 L'interface Future<V> . . . . . . . . . . . . . . . . . . . . . . . . . 10
1.5.4 Ambiguïtés et diérences clés . . . . . . . . . . . . . . . . . . . . . 10
1.5.5 Récapitulatif . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12
1.5.6 Exercices du Chapitre 1 . . . . . . . . . . . . . . . . . . . . . . . . 12
2 Création et Gestion des Threads en Java 14
2.1 Les deux approches de création . . . . . . . . . . . . . . . . . . . . . . . . 14
2.1.1 Étendre la classe Thread . . . . . . . . . . . . . . . . . . . . . . . . 14
2.1.2 Implémenter l'interface Runnable (recommandé) . . . . . . . . . . . 14
2.2 Cycle de vie d'un Thread . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15
2.3 Méthodes principales de Thread . . . . . . . . . . . . . . . . . . . . . . . . 19
2.3.1 start() et run() . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19
2.3.2 sleep(long millis) . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20
2.3.3 join() . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20
2.3.4 interrupt() . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21
2.3.5 Récapitulatif . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21
2.3.6 Exercices du Chapitre 2 . . . . . . . . . . . . . . . . . . . . . . . . 22
3 Programmation Asynchrone Non-Bloquante 23
3.1 Qu'est-ce que la programmation asynchrone ? . . . . . . . . . . . . . . . . 23
3.2 CompletableFuture . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24
3.2.1 Création . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24
3.2.2 Chaînage (Pipeline) . . . . . . . . . . . . . . . . . . . . . . . . . . . 25
3.2.3 Combinaison de plusieurs futures . . . . . . . . . . . . . . . . . . . 26
3.2.4 Gestion des erreurs . . . . . . . . . . . . . . . . . . . . . . . . . . . 26
3.3 Exemple complet : Usine de Chaussures . . . . . . . . . . . . . . . . . . . . 28
1
TABLE DES MATIÈRES 2
3.4 Flow API (Java 9+) Reactive Streams . . . . . . . . . . . . . . . . . . . 31
3.4.1 Récapitulatif . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32
3.4.2 Exercices du Chapitre 3 . . . . . . . . . . . . . . . . . . . . . . . . 32
4 Exclusion Mutuelle et Synchronisation 33
4.1 Le problème de la Race Condition . . . . . . . . . . . . . . . . . . . . . . . 33
4.2 Le mot-clé synchronized . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37
4.2.1 Méthode synchronized . . . . . . . . . . . . . . . . . . . . . . . . . 38
4.2.2 Bloc synchronized . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38
4.3 [Link] . . . . . . . . . . . . . . . . . . . . . . . . . . . . 39
4.3.1 ReentrantLock . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 39
4.3.2 ReadWriteLock . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 39
4.3.3 Récapitulatif . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40
4.3.4 Exercices du Chapitre 3 . . . . . . . . . . . . . . . . . . . . . . . . 41
5 Visibilité et Modèle Mémoire Java 42
5.1 Le problème de visibilité . . . . . . . . . . . . . . . . . . . . . . . . . . . . 42
5.2 Le mot-clé volatile . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 43
5.3 Java Memory Model (JMM) . . . . . . . . . . . . . . . . . . . . . . . . . . 43
5.4 Classes Atomiques . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 44
5.4.1 Récapitulatif . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 44
5.4.2 Exercices du Chapitre 4 . . . . . . . . . . . . . . . . . . . . . . . . 45
6 Atomicité et mécanisme CAS 46
6.1 Qu'est-ce que l'atomicité ? . . . . . . . . . . . . . . . . . . . . . . . . . . . 46
6.2 Le mécanisme Compare-And-Swap (CAS) . . . . . . . . . . . . . . . . . . 46
6.3 [Link] en détail . . . . . . . . . . . . . . . . . . . . . . 47
6.3.1 Récapitulatif . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 48
6.3.2 Exercices du Chapitre 5 . . . . . . . . . . . . . . . . . . . . . . . . 48
7 Immutabilité 49
7.1 Pourquoi l'immutabilité ? . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49
7.1.1 Records Java (Java 16+) . . . . . . . . . . . . . . . . . . . . . . . . 50
7.1.2 Collections immuables . . . . . . . . . . . . . . . . . . . . . . . . . 50
7.1.3 Récapitulatif . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 51
7.1.4 Exercices du Chapitre 6 . . . . . . . . . . . . . . . . . . . . . . . . 51
8 Coordination entre Threads 52
8.1 wait(), notify() et notifyAll() . . . . . . . . . . . . . . . . . . . . . . . . . . 52
8.2 Outils [Link] . . . . . . . . . . . . . . . . . . . . . . . . . . . 54
8.2.1 CountDownLatch . . . . . . . . . . . . . . . . . . . . . . . . . . . . 54
8.2.2 CyclicBarrier . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55
8.2.3 Semaphore . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 56
8.2.4 BlockingQueue . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 56
8.2.5 Récapitulatif . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 57
8.2.6 Exercices du Chapitre 7 . . . . . . . . . . . . . . . . . . . . . . . . 57
Concurrency & Multithreading en Java Dr. BADR EL KHALYLY
TABLE DES MATIÈRES 3
9 Gestion des Tâches : Executor Framework 59
9.1 Problématique . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 59
9.2 Runnable vs Callable . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 59
9.3 ExecutorService . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 59
9.4 Future . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 60
9.4.1 Récapitulatif . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 61
9.4.2 Exercices du Chapitre 8 . . . . . . . . . . . . . . . . . . . . . . . . 62
10 Parallélisme 63
10.1 Fork/Join Framework . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 63
10.2 Parallel Streams . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 64
10.2.1 Récapitulatif . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 65
10.2.2 Exercices du Chapitre 9 . . . . . . . . . . . . . . . . . . . . . . . . 65
11 Threads Virtuels (Project Loom Java 21+) 66
11.1 Problématique des Platform Threads . . . . . . . . . . . . . . . . . . . . . 66
11.2 Comment fonctionnent les Virtual Threads ? . . . . . . . . . . . . . . . . . 67
11.3 Utilisation en Java 21+ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 68
11.3.1 Récapitulatif . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 70
11.3.2 Exercices du Chapitre 11 . . . . . . . . . . . . . . . . . . . . . . . . 70
12 Exercices et Challenges Corrigés 71
12.1 Challenge 1 : Compteur thread-safe . . . . . . . . . . . . . . . . . . . . . . 71
12.2 Challenge 2 : Producteur-Consommateur . . . . . . . . . . . . . . . . . . . 72
12.3 Challenge 3 : Pipeline asynchrone CompletableFuture . . . . . . . . . . . . 73
12.4 Challenge 4 : Deadlock et résolution . . . . . . . . . . . . . . . . . . . . . . 75
12.5 Challenge 5 : Usine de Chaussures Améliorée . . . . . . . . . . . . . . . . . 77
Concurrency & Multithreading en Java Dr. BADR EL KHALYLY
Chapitre 1
Fondamentaux : Programme, Processus,
Thread et Tâche
1.1 Introduction
Avant de plonger dans le multithreading Java, il est essentiel de comprendre les
concepts fondamentaux qui sous-tendent l'exécution concurrente. Ce chapitre établit les
bases en dénissant clairement les notions de Programme, Processus, Thread et
Tâche, ainsi que les classes et interfaces Java associées.
1.2 Programme
Dénition: Programme
Un programme est un ensemble d'instructions écrites dans un langage de pro-
grammation, stocké sous forme de chier sur un support de stockage (disque dur,
SSD). C'est une entité statique et passive il ne fait rien tant qu'il n'est pas
exécuté.
Caractéristiques d'un programme :
Entité statique : existe sous forme de chier (ex : [Link], [Link])
Contient du code compilé (bytecode Java) ou interprétable (script Python)
Ne consomme aucune ressource CPU ni mémoire vive tant qu'il n'est pas
lancé
Peut être copié, déplacé, supprimé comme tout chier ordinaire
En Java, après compilation, le programme est un ensemble de chiers .class ou un
.jar
Exemple concret : le chier [Link] sur votre disque est un programme.
Il ne consomme aucune ressource tant que vous ne tapez pas java HelloWorld dans le
terminal.
4
CHAPITRE 1. FONDAMENTAUX : PROGRAMME, PROCESSUS, THREAD ET
TÂCHE 5
Problématique: Qu'est-ce qui donne vie à un programme ?
Un programme seul est inerte. Comment le système d'exploitation transforme-t-il
ce chier statique en quelque chose qui s'exécute réellement ? Comment plusieurs
utilisateurs peuvent-ils lancer le même programme simultanément, chacun avec ses
propres données ? C'est la notion de processus qui répond à ces questions.
1.3 Processus
Dénition: Processus
Un processus est une instance d'un programme en cours d'exécution. C'est une en-
tité dynamique et active qui possède son propre espace mémoire, ses propres res-
sources et un état d'exécution. Le système d'exploitation crée un processus chaque
fois qu'un programme est lancé.
Caractéristiques d'un processus :
Entité dynamique : créé au lancement, détruit à la n de l'exécution
Possède son propre espace d'adressage mémoire isolé des autres processus
Contient au moins un thread (le thread principal ou main thread )
Identié par un PID (Process ID) unique attribué par l'OS
Possède ses propres ressources : descripteurs de chiers, sockets réseau, variables
d'environnement
La communication entre processus (IPC Inter-Process Communication) né-
cessite des mécanismes spéciaux : pipes, sockets, mémoire partagée, chiers
Exemple concret : quand vous exécutez java HelloWorld, l'OS crée un processus
JVM. Si vous lancez la commande deux fois, vous avez deux processus indépendants avec
deux espaces mémoire séparés.
Classes Java associées :
ProcessBuilder : classe permettant de congurer et lancer un nouveau processus
OS depuis Java (commande, répertoire de travail, variables d'environnement)
Process : classe représentant un processus OS en cours d'exécution ; ore les mé-
thodes pid(), waitFor(), destroy(), getInputStream()
Problématique: Limitation des processus
Les processus sont lourds à créer (appel système coûteux) et consomment beau-
coup de mémoire car chacun a son propre espace d'adressage. La communication
entre processus (IPC) est complexe et lente. Comment exécuter plusieurs tâches
concurrentes au sein d'un même processus de manière légère et avec un partage
de mémoire facile ? La réponse : les threads.
Concurrency & Multithreading en Java Dr. BADR EL KHALYLY
CHAPITRE 1. FONDAMENTAUX : PROGRAMME, PROCESSUS, THREAD ET
TÂCHE 6
Table 1.1 Comparaison Programme vs Processus
Critère Programme Processus
Nature Statique (chier sur disque) Dynamique (en exécution en mé-
moire)
Mémoire Aucune (stocké sur disque) Espace mémoire propre (heap,
stack, code)
Durée de vie Permanente (tant que le chier Temporaire (début → n d'exé-
existe) cution)
Ressources CPU Aucune Allouées par le scheduler de l'OS
Instances Un seul chier Multiples processus du même
programme possibles
Communication IPC : pipes, sockets, mémoire
partagée
1.4 Thread
Dénition: Thread
Un thread (l d'exécution) est la plus petite unité d'exécution planiable par
le système d'exploitation, au sein d'un processus. Plusieurs threads d'un même
processus partagent le même espace mémoire (heap) tout en ayant chacun
leur propre pile d'exécution (stack) et compteur de programme (Program
Counter).
Caractéristiques d'un thread :
Léger : la création d'un thread et le changement de contexte (context switch ) sont
beaucoup plus rapides que pour un processus
Partage mémoire : tous les threads d'un processus partagent le heap (objets,
variables d'instance) et la zone de code
Pile propre : chaque thread possède sa propre stack contenant ses variables locales
et sa pile d'appels
Exécution concurrente : sur un CPU multi-c÷urs, plusieurs threads s'exécutent
véritablement en parallèle
Ordonnancement : le scheduler de l'OS décide quel thread s'exécute et quand
l'ordre est non déterministe
1.4.1 La classe [Link]
La classe Thread est la classe fondamentale de Java pour représenter et manipuler un
l d'exécution. C'est une classe concrète (pas une interface) qui implémente elle-même
Runnable.
Attributs principaux d'un Thread :
name : nom du thread (par défaut Thread-0, Thread-1, etc.)
priority : priorité d'exécution (1 à 10, défaut 5 NORM_PRIORITY)
daemon : un thread daemon n'empêche pas la JVM de s'arrêter
Concurrency & Multithreading en Java Dr. BADR EL KHALYLY
CHAPITRE 1. FONDAMENTAUX : PROGRAMME, PROCESSUS, THREAD ET
TÂCHE 7
state : l'état courant du thread (voir section suivante)
threadGroup : le groupe auquel appartient le thread
1.4.2 Les 6 états d'un Thread ([Link])
Table 1.2 Les 6 états d'un thread Java
État Description Transition depuis
NEW Créé mais start() pas encore appelé Construction
RUNNABLE Prêt ou en cours d'exécution sur le CPU start()
BLOCKED Attend l'acquisition d'un verrou (monitor) Entrée synchronized
WAITING Attend indéniment un signal wait(), join()
TIMED_WAITING Attend avec un délai maximum sleep(ms), wait(ms)
TERMINATED Exécution terminée (normalement ou par ex- Fin de run()
ception)
1.4.3 Méthodes principales de la classe Thread
Table 1.3 Méthodes d'instance de Thread
Méthode Signature Description
start() void start() Démarre le thread : crée un nou-
veau l d'exécution OS et appelle
run(). Ne peut être appelé qu'une
seule fois.
run() void run() Contient le code à exécuter. Ne ja-
mais l'appeler directement (sinon
exécution dans le thread appelant).
join() void join() Bloque le thread appelant jusqu'à
la terminaison du thread cible. Va-
riantes : join(long ms).
interrupt() void interrupt() Positionne le ag d'interruption. Si
le thread est ensleep/wait/join,
lance uneInterruptedException.
isAlive() boolean isAlive() Retourne true si le thread a été dé-
marré et n'est pas encore terminé.
isInterrupted() boolean isInterrupted() Vérie le ag d'interruption sans le
réinitialiser.
getState() [Link] getState() Retourne l'état courant du thread
(NEW, RUNNABLE, etc.).
setDaemon() void setDaemon(boolean) Marque comme daemon. Doit être
appelé avant start().
Concurrency & Multithreading en Java Dr. BADR EL KHALYLY
CHAPITRE 1. FONDAMENTAUX : PROGRAMME, PROCESSUS, THREAD ET
TÂCHE 8
Table 1.4 Méthodes statiques de Thread
Méthode Signature Description
sleep() static void sleep(long ms) Met le thread courant en
pause pour la durée spéciée.
Ne libère pas les verrous dé-
tenus.
currentThread() static Thread currentThread() Retourne la référence vers le
thread en cours d'exécution.
yield() static void yield() Suggère au scheduler de don-
ner le CPU à un autre thread
de même priorité. Aucune
garantie.
interrupted() static boolean interrupted() Vérie le ag d'interruption
du thread courant et le ré-
initialise à false.
1.4.4 Ambiguïtés et pièges fréquents
Note: start() vs run() La confusion classique
start() : crée un nouveau l d'exécution OS, puis la JVM appelle run()
dans ce nouveau thread
run() : exécute le code dans le thread appelant (pas de nouveau thread
créé)
Appeler run() directement est une erreur logique courante : le code s'exécute
séquentiellement dans le thread main, sans aucune concurrence
Note: sleep() vs wait() Deux mécanismes diérents
[Link](ms) : méthode statique de Thread ; met le thread courant en
pause ;ne libère PAS les verrous détenus ; utilisé pour une pause temporelle
[Link]() : méthode d'instance de Object ; doit être appelé dans un
bloc synchronized ; libère le verrou de l'objet ; utilisé pour la coordina-
tion entre threads (attend un notify)
Confusion fréquente : utiliser sleep() pour attendre un événement au lieu de
wait/notify
Note: interrupted() vs isInterrupted() Attention au ag
[Link]() : méthode statique ; teste le ag du thread courant
et le réinitialise à false
[Link]() : méthode d'instance ; teste le ag sans le réini-
tialiser
Piège : appeler [Link]() deux fois de suite le deuxième appel
retourne toujours false
Concurrency & Multithreading en Java Dr. BADR EL KHALYLY
CHAPITRE 1. FONDAMENTAUX : PROGRAMME, PROCESSUS, THREAD ET
TÂCHE 9
Note: Thread daemon vs Thread utilisateur
Thread utilisateur (défaut) : la JVM attend la n de tous les threads
utilisateur avant de s'arrêter
Thread daemon : thread de service en arrière-plan (ex : garbage collector) ;
la JVM s'arrête même si des threads daemon sont encore actifs
setDaemon(true) doit être appelé avant start(), sinon
IllegalThreadStateException
Problématique: Problèmes liés aux threads
Le partage de mémoire entre threads introduit des problèmes majeurs de la pro-
grammation concurrente :
Race condition (condition de course) : deux threads lisent et modient une
variable partagée simultanément, produisant un résultat imprévisible
Deadlock (interblocage) : deux threads s'attendent mutuellement indéni-
ment, chacun détenant un verrou dont l'autre a besoin
Starvation (famine) : un thread ne reçoit jamais le CPU car d'autres threads
de priorité plus haute l'en empêchent
Livelock : deux threads réagissent en boucle l'un à l'autre sans progresser
(comme deux personnes qui se cèdent le passage indéniment)
Incohérence mémoire : un thread ne voit pas les modications eectuées
par un autre à cause du cache CPU
Ces problèmes constituent le c÷ur de ce cours et seront traités dans les chapitres
suivants.
1.5 Tâche (Task)
Dénition: Tâche
Une tâche (Task) est une unité logique de travail à accomplir. Contrairement au
thread qui est un mécanisme d'exécution, la tâche est le travail lui-même. En Java,
une tâche est représentée par les interfaces Runnable ou Callable<T>. Une tâche
est indépendante du thread qui l'exécute elle peut être soumise à n'importe
quel thread ou pool de threads.
1.5.1 L'interface Runnable
Dénition: [Link]
Runnable est une interface fonctionnelle (une seule méthode abstraite) du pa-
ckage [Link]. Elle représente une tâche sans résultat et sans exception
checked.
Méthode unique : void run() contient le code de la tâche
Concurrency & Multithreading en Java Dr. BADR EL KHALYLY
CHAPITRE 1. FONDAMENTAUX : PROGRAMME, PROCESSUS, THREAD ET
TÂCHE 10
Pas de valeur de retour (void)
Ne peut pas lancer d'exception vériée (checked exception )
Compatible avec les expressions lambda depuis Java 8
Peut être passée au constructeur de Thread ou soumise à un ExecutorService
1.5.2 L'interface Callable<V>
Dénition: [Link]<V>
Callable<V> est une interface fonctionnelle du package
[Link]. Elle représente une tâche avec résultat et avec
exception.
Méthode unique : V call() throws Exception exécute la tâche et retourne
un résultat
Retourne une valeur de type V
Peut lancer n'importe quelle exception (y compris les checked exceptions)
Ne peut PAS être passée directement au constructeur de Thread doit être
soumise à un ExecutorService
1.5.3 L'interface Future<V>
Dénition: [Link]<V>
Future<V> représente le résultat futur d'une tâche asynchrone. C'est un ticket
pour récupérer le résultat quand il sera disponible.
V get() : attend et retourne le résultat ( bloquant)
V get(long timeout, TimeUnit unit) : attend avec un délai maximum
boolean isDone() : vérie si la tâche est terminée (non bloquant)
boolean cancel(boolean mayInterrupt) : tente d'annuler la tâche
boolean isCancelled() : vérie si la tâche a été annulée
1.5.4 Ambiguïtés et diérences clés
Note: Runnable vs Callable Quand utiliser lequel ?
Runnable : quand la tâche ne retourne aucun résultat et ne lance pas d'ex-
ception checked (ex : achage, logging, mise à jour d'état)
Callable<V> : quand la tâche doit retourner un résultat ou peut lancer
une exception checked (ex : calcul, requête réseau, lecture chier)
Runnable est dans [Link] (disponible partout) ; Callable est dans
[Link] (nécessite un import)
Runnable peut être passée à new Thread(runnable) ; Callable ne peut pas
Concurrency & Multithreading en Java Dr. BADR EL KHALYLY
CHAPITRE 1. FONDAMENTAUX : PROGRAMME, PROCESSUS, THREAD ET
TÂCHE 11
Note: Thread (classe) vs Runnable (interface) Deux rôles distincts
Thread véhicule (le mécanisme d'exécution, le l)
est le
Runnable est le passager (le travail à eectuer, la tâche)
Hériter de Thread : la classe EST un thread (couplage fort, empêche un autre
héritage)
Implémenter Runnable : la classe DÉFINIT une tâche (découplage, réutili-
sable, peut étendre une autre classe)
Bonne pratique : toujours préférer Runnable (ou Callable) à l'héritage de
Thread
Note: Runnable vs Thread Pourquoi Thread implémente Runnable ?
La classe Thread implémente l'interface Runnable. Cela signie que Thread est à
la fois un mécanisme d'exécution et une tâche. Cette double nature est source de
confusion :
Quand on hérite de Thread, on redénit run() directement sur le thread
Quand on passe un Runnable au constructeur, le thread délègue l'exécution
au Runnable
Si on fait les deux (hérite de Thread ET passe un Runnable), c'est le run()
redéni qui gagne (le Runnable est ignoré)
Table 1.5 Comparaison complète : Thread, Runnable, Callable, Future
Critère Thread Runnable Callable<V> Future<V>
Type Classe Interface Interface Interface
Package [Link] [Link] [Link] [Link]
Méthode run() run() call() get()
Résultat Non Non Oui (V) Oui (V)
Exception Non Non Oui ExecutionException
Rôle Exécuteur Tâche Tâche Résultat futur
Table 1.6 Comparaison Processus vs Thread
Critère Processus Thread
Mémoire Espace d'adressage isolé Partage le heap du processus
Création Lourde (appel système coû- Légère (beaucoup plus ra-
teux) pide)
Communication IPC complexe (pipes, so- Directe via mémoire parta-
ckets) gée
Crash Un processus qui crashe n'af- Un thread qui crashe peut
fecte pas les autres faire tomber tout le proces-
sus
Changement de contexte Coûteux (changement d'es- Rapide (même espace mé-
pace mémoire) moire)
Concurrency & Multithreading en Java Dr. BADR EL KHALYLY
CHAPITRE 1. FONDAMENTAUX : PROGRAMME, PROCESSUS, THREAD ET
TÂCHE 12
Problématique: Gestion manuelle des threads
Créer et gérer manuellement les threads (new Thread()) pour chaque tâche est
inecace et dangereux :
Coût de création : chaque thread alloue ∼1 Mo de stack et eectue un appel
système
Nombre incontrôlé : risque de créer trop de threads → OutOfMemoryError
Pas de réutilisation : le thread est détruit après exécution, gaspillant les
ressources
Pas de le d'attente : impossible de limiter le nombre de tâches en cours
Comment gérer ecacement un grand nombre de tâches ? La réponse : les Executor
Frameworks et les Thread Pools.
1.5.5 Récapitulatif
Récapitulatif du chapitre
Programme : entité statique (chier .class sur disque) passif, pas de
ressources
Processus : instance dynamique d'un programme en exécution, avec mémoire
isolée (PID)
Thread : unité d'exécution légère au sein d'un processus, partageant le heap
Tâche : unité logique de travail (Runnable ou Callable), découplée du thread
Future : ticket pour récupérer le résultat d'une tâche asynchrone
La relation : Programme → Processus → Threads → chaque Thread exécute
des Tâches
Confusions à éviter : start() vs run(), sleep() vs wait(), Thread (classe)
vs Runnable (interface)
1.5.6 Exercices du Chapitre 1
Exercice: 1.1 Questions de compréhension
1. Quelle est la diérence fondamentale entre un programme et un processus ?
2. Pourquoi dit-on qu'un thread est léger par rapport à un processus ?
3. Expliquez pourquoi le partage mémoire entre threads est à la fois un avantage
et un inconvénient.
4. Quelle est la diérence entre Runnable et Callable ? Quand utiliser l'un ou
l'autre ?
5. Pourquoi ne doit-on jamais appeler run() directement sur un thread ?
6. Expliquez la diérence entre [Link]() [Link]().
7. Quelle est la diérence entre [Link]() et
[Link]() ?
Concurrency & Multithreading en Java Dr. BADR EL KHALYLY
CHAPITRE 1. FONDAMENTAUX : PROGRAMME, PROCESSUS, THREAD ET
TÂCHE 13
8. Un thread daemon peut-il empêcher la JVM de s'arrêter ? Justiez.
9. Pourquoi Callable ne peut-il pas être passé directement au constructeur de
Thread ?
10. Que se passe-t-il si on appelle start() deux fois sur le même thread ?
Exercice: 1.2 Vrai ou Faux
Répondez par Vrai ou Faux et justiez :
1. Un processus peut exister sans aucun thread.
2. Deux threads du même processus peuvent accéder à la même variable d'ins-
tance.
3. [Link](1000) libère le verrou détenu par le thread.
4. Callable est une interface du package [Link].
5. [Link]() est une méthode non bloquante.
6. Un thread dans l'état NEW consomme du temps CPU.
7. Runnable et Callable sont toutes les deux des interfaces fonctionnelles.
Concurrency & Multithreading en Java Dr. BADR EL KHALYLY
Chapitre 2
Création et Gestion des Threads en Java
2.1 Les deux approches de création
2.1.1 Étendre la classe Thread
Listing 2.1 Création par héritage de Thread
1 public class MonThread extends Thread {
2 private String nom ;
3
4 public MonThread ( String nom ) {
5 this . nom = nom ;
6 }
7
8 @Override
9 public void run () {
10 for ( int i = 0; i < 5; i ++) {
11 System . out . println ( nom + " - iteration " + i);
12 try {
13 Thread . sleep (100) ;
14 } catch ( InterruptedException e ) {
15 Thread . currentThread () . interrupt () ;
16 return ;
17 }
18 }
19 }
20
21 public static void main ( String [] args ) {
22 MonThread t1 = new MonThread ( " Alpha " );
23 MonThread t2 = new MonThread ( " Beta ") ;
24 t1 . start () ; // NE PAS appeler run () directement !
25 t2 . start () ;
26 }
27 }
2.1.2 Implémenter l'interface Runnable (recommandé)
14
CHAPITRE 2. CRÉATION ET GESTION DES THREADS EN JAVA 15
Listing 2.2 Création par implémentation de Runnable
1 public class MonRunnable implements Runnable {
2 private String nom ;
3
4 public MonRunnable ( String nom ) {
5 this . nom = nom ;
6 }
7
8 @Override
9 public void run () {
10 for ( int i = 0; i < 5; i ++) {
11 System . out . println ( nom + " - iteration " + i);
12 }
13 }
14
15 public static void main ( String [] args ) {
16 Thread t1 = new Thread ( new MonRunnable ( " Alpha " ));
17 Thread t2 = new Thread ( new MonRunnable ( " Beta ") );
18 // Ou avec lambda :
19 Thread t3 = new Thread (() -> System . out . println (" Gamma ") );
20
21 t1 . start () ;
22 t2 . start () ;
23 t3 . start () ;
24 }
25 }
Note: Pourquoi préférer Runnable ?
Java ne supporte pas l'héritage multiple : étendre Thread empêche d'étendre
une autre classe
Runnable sépare la tâche du mécanisme d'exécution (meilleur design)
Une tâche Runnable peut être soumise à un ExecutorService
Compatible avec les expressions lambda (Java 8+)
2.2 Cycle de vie d'un Thread
Un thread Java passe par les états suivants :
Concurrency & Multithreading en Java Dr. BADR EL KHALYLY
CHAPITRE 2. CRÉATION ET GESTION DES THREADS EN JAVA 16
Concurrency & Multithreading en Java Dr. BADR EL KHALYLY
CHAPITRE 2. CRÉATION ET GESTION DES THREADS EN JAVA 17
Table 2.1 Les 6 états d'un thread Java ( [Link])
État Description
NEW Thread créé mais pas encore démarré (start() pas appelé)
RUNNABLE Thread prêt à s'exécuter ou en cours d'exécution
BLOCKED Thread bloqué en attente d'un verrou (monitor lock)
WAITING Thread en attente indénie (wait(), join())
TIMED_WAITING Thread en attente avec timeout (sleep(), wait(ms))
TERMINATED Thread qui a ni son exécution
Concurrency & Multithreading en Java Dr. BADR EL KHALYLY
CHAPITRE 2. CRÉATION ET GESTION DES THREADS EN JAVA 18
Concurrency & Multithreading en Java Dr. BADR EL KHALYLY
CHAPITRE 2. CRÉATION ET GESTION DES THREADS EN JAVA 19
2.3 Méthodes principales de Thread
2.3.1 start() et run()
Concurrency & Multithreading en Java Dr. BADR EL KHALYLY
CHAPITRE 2. CRÉATION ET GESTION DES THREADS EN JAVA 20
Listing 2.3 Diérence entre start() et run()
1 public class StartVsRun {
2 public static void main ( String [] args ) {
3 Thread t = new Thread (() -> {
4 System . out . println ( " Executing in : " +
5 Thread . currentThread () . getName () );
6 }) ;
7
8 // MAUVAIS : run () s ' execute dans le thread MAIN
9 t. run () ; // Affiche : Executing in : main
10
11 // BON : start () cree un NOUVEAU thread
12 t. start () ; // Affiche : Executing in : Thread -0
13 }
14 }
2.3.2 sleep(long millis)
Listing 2.4 Utilisation de sleep()
1 public class SleepExample {
2 public static void main ( String [] args ) {
3 Thread t = new Thread (() -> {
4 for ( int i = 1; i <= 5; i ++) {
5 System . out . println (" Etape " + i) ;
6 try {
7 Thread . sleep (1000) ; // Pause de 1 seconde
8 } catch ( InterruptedException e) {
9 System . out . println (" Thread interrompu !" );
10 Thread . currentThread () . interrupt () ;
11 return ;
12 }
13 }
14 }) ;
15 t. start () ;
16 }
17 }
2.3.3 join()
Listing 2.5 Utilisation de join() pour attendre un thread
1 public class JoinExample {
2 public static void main ( String [] args ) throws
InterruptedException {
3 Thread calcul = new Thread (() -> {
4 System . out . println ( " Calcul en cours ... " );
5 try { Thread . sleep (3000) ; }
6 catch ( InterruptedException e) { return ; }
Concurrency & Multithreading en Java Dr. BADR EL KHALYLY
CHAPITRE 2. CRÉATION ET GESTION DES THREADS EN JAVA 21
7 System . out . println ( " Calcul termine ! ");
8 }) ;
9
10 calcul . start () ;
11 System . out . println (" Main attend le calcul ... ");
12 calcul . join () ; // BLOQUE main jusqu 'a la fin de calcul
13 System . out . println (" Main reprend apres join () ") ;
14 }
15 }
2.3.4 interrupt()
Listing 2.6 Interruption propre d'un thread
1 public class InterruptExample {
2 public static void main ( String [] args ) throws
InterruptedException {
3 Thread worker = new Thread (() -> {
4 while (! Thread . currentThread () . isInterrupted () ) {
5 System . out . println (" Travail en cours ... ") ;
6 try {
7 Thread . sleep (500) ;
8 } catch ( InterruptedException e) {
9 System . out . println (" Interruption recue ! ");
10 Thread . currentThread () . interrupt () ; // Re - set
flag
11 break ;
12 }
13 }
14 System . out . println ( " Worker arrete proprement . ");
15 }) ;
16
17 worker . start () ;
18 Thread . sleep (2000) ; // Laisse travailler 2 secondes
19 worker . interrupt () ; // Demande l ' arret
20 worker . join () ;
21 System . out . println (" Programme termine .") ;
22 }
23 }
2.3.5 Récapitulatif
Récapitulatif du chapitre
Deux manières de créer un thread : hériter de Thread ou implémenter
Runnable
Toujours appeler start() et non run() directement
sleep(ms) : met le thread en pause (TIMED_WAITING)
Concurrency & Multithreading en Java Dr. BADR EL KHALYLY
CHAPITRE 2. CRÉATION ET GESTION DES THREADS EN JAVA 22
join() : attend la terminaison d'un autre thread (WAITING)
interrupt() : demande poliment l'arrêt d'un thread
→ RUNNABLE → RUNNING → (BLO-
Un thread passe par 6 états : NEW
CKED/WAITING/TIMED_WAITING) → TERMINATED
2.3.6 Exercices du Chapitre 2
Exercice: 2.1 Chronomètre multi-thread
Créez un chronomètre qui :
1. Lance un thread qui ache le temps écoulé chaque seconde
2. Lance un second thread qui attend une saisie utilisateur pour arrêter le chro-
nomètre
3. Utilisez interrupt() pour arrêter le thread du chronomètre proprement
Exercice: 2.2 Course de threads
Simulez une course entre 5 threads. Chaque thread représente un coureur qui avance
d'une distance aléatoire (1-10) à chaque itération avec un sleep() aléatoire. Le
premier à atteindre 100 gagne. Achez le classement nal en utilisant join().
Concurrency & Multithreading en Java Dr. BADR EL KHALYLY
Chapitre 3
Programmation Asynchrone Non-Bloquante
3.1 Qu'est-ce que la programmation asynchrone ?
Problématique: Le problème du blocage
Dans un modèle synchrone (bloquant), quand un thread eectue une opération
longue (appel réseau, lecture chier, requête BD), il est bloqué et ne fait rien
en attendant la réponse. Avec 1000 requêtes concurrentes, il faudrait 1000 threads
bloqués, ce qui est un gaspillage massif de ressources.
Dénition: Programmation Asynchrone Non-Bloquante
La programmation asynchrone non-bloquante permet à un thread de lancer
une opération et de continuer son exécution sans attendre le résultat. Quand le
résultat est disponible, un callback ou une continuation est exécuté. Cela permet
à un petit nombre de threads de gérer un grand nombre d'opérations concurrentes.
Table 3.1 Synchrone bloquant vs Asynchrone non-bloquant
Aspect Synchrone (Bloquant) Asynchrone (Non-Bloquant)
Thread Bloqué pendant l'attente Libre, continue d'autres tâches
Scalabilité 1 thread par opération Peu de threads, beaucoup d'opéra-
tions
Résultat Retourné directement Via callback / Future / Promise
Code Séquentiel, simple Pipeline, composition de stages
Idéal pour Peu de requêtes, CPU-bound Beaucoup de requêtes, I/O-bound
Listing 3.1 Synchrone vs Asynchrone
1 // === APPROCHE SYNCHRONE ( BLOQUANTE ) ===
2 String data = fetchFromDB () ; // Thread BLOQUE 200 ms
3 String result = process ( data ); // Thread BLOQUE pendant calcul
4 sendToAPI ( result ) ; // Thread BLOQUE 300 ms
5 // Total : le thread est bloque ~500 ms +
6
7 // === APPROCHE ASYNCHRONE ( NON - BLOQUANTE ) ===
23
CHAPITRE 3. PROGRAMMATION ASYNCHRONE NON-BLOQUANTE 24
8 CompletableFuture
9 . supplyAsync (() -> fetchFromDB () ) // Lance sans bloquer
10 . thenApply ( data -> process ( data ) ) // Chaine le traitement
11 . thenAccept ( result -> sendToAPI ( result ) ) // Chaine l ' envoi
12 . exceptionally ( ex -> { log ( ex ) ; return null ; }) ; // Erreurs
13 // Le thread principal est LIBRE immediatement !
3.2 CompletableFuture
Figure 3.1 Diagramme de séquence : Pipeline CompletableFuture
3.2.1 Création
Listing 3.2 Créer un CompletableFuture
1 import java . util . concurrent .*;
2
3 public class CFCreation {
4 public static void main ( String [] args ) throws Exception {
5 // 1. supplyAsync : tache avec resultat
6 CompletableFuture < String > cf1 = CompletableFuture
7 . supplyAsync (() -> {
8 // Execute dans ForkJoinPool . commonPool ()
Concurrency & Multithreading en Java Dr. BADR EL KHALYLY
CHAPITRE 3. PROGRAMMATION ASYNCHRONE NON-BLOQUANTE 25
9 return " Hello " + Thread . currentThread () . getName () ;
10 }) ;
11
12 // 2. runAsync : tache sans resultat
13 CompletableFuture < Void > cf2 = CompletableFuture
14 . runAsync (() -> System . out . println (" Tache
asynchrone ") );
15
16 // 3. Avec un executor personnalise
17 ExecutorService executor = Executors . newFixedThreadPool (4) ;
18 CompletableFuture < Integer > cf3 = CompletableFuture
19 . supplyAsync (() -> 42 , executor );
20
21 // 4. Deja complete
22 CompletableFuture < String > cf4 =
23 CompletableFuture . completedFuture (" Deja pret " );
24
25 System . out . println ( cf1 . get () );
26 executor . shutdown () ;
27 }
28 }
3.2.2 Chaînage (Pipeline)
Listing 3.3 Chaînage de CompletableFuture
1 public class CFChaining {
2 public static void main ( String [] args ) throws Exception {
3 CompletableFuture < String > pipeline = CompletableFuture
4 . supplyAsync (() -> " Hello World " )
5 // thenApply : transforme le resultat ( Function )
6 . thenApply (s -> s . trim () )
7 . thenApply (s -> s . toUpperCase () )
8 // thenCompose : chaine avec un autre CF ( flatMap )
9 . thenCompose ( s -> CompletableFuture . supplyAsync (
10 () -> s + " - traite par " +
11 Thread . currentThread () . getName ()
12 ));
13
14 // thenAccept : consomme le resultat ( Consumer )
15 pipeline . thenAccept ( System . out :: println );
16
17 // thenRun : execute une action sans resultat
18 pipeline . thenRun (() -> System . out . println (" Pipeline
termine " ));
19
20 pipeline . join () ; // Attend la fin
21 }
22 }
Concurrency & Multithreading en Java Dr. BADR EL KHALYLY
CHAPITRE 3. PROGRAMMATION ASYNCHRONE NON-BLOQUANTE 26
3.2.3 Combinaison de plusieurs futures
Listing 3.4 Combiner plusieurs CompletableFuture
1 public class CFCombine {
2 public static void main ( String [] args ) throws Exception {
3 CompletableFuture < String > nom = CompletableFuture
4 . supplyAsync (() -> { sleep (1000) ; return " Jean "; }) ;
5 CompletableFuture < Integer > age = CompletableFuture
6 . supplyAsync (() -> { sleep (800) ; return 30; }) ;
7 CompletableFuture < String > ville = CompletableFuture
8 . supplyAsync (() -> { sleep (1200) ; return " Paris " ; }) ;
9
10 // thenCombine : combiner 2 resultats
11 CompletableFuture < String > profil = nom
12 . thenCombine ( age , (n , a) -> n + " (" + a + " ans ) ");
13
14 // allOf : attendre que TOUS soient termines
15 CompletableFuture < Void > tous = CompletableFuture
16 . allOf ( nom , age , ville );
17 tous . thenRun (() -> {
18 System . out . println ( nom . join () + " , " +
19 age . join () + " , " + ville . join () );
20 }) ;
21
22 // anyOf : le PREMIER a terminer
23 CompletableFuture < Object > premier = CompletableFuture
24 . anyOf ( nom , age , ville );
25 System . out . println (" Premier : " + premier . get () ) ;
26
27 tous . join () ;
28 }
29
30 static void sleep ( long ms ) {
31 try { Thread . sleep ( ms ) ; } catch ( InterruptedException e) {}
32 }
33 }
3.2.4 Gestion des erreurs
Listing 3.5 Gestion des erreurs avec CompletableFuture
1 public class CFErrors {
2 public static void main ( String [] args ) {
3 CompletableFuture < Integer > cf = CompletableFuture
4 . supplyAsync (() -> {
5 if ( Math . random () > 0.5)
6 throw new RuntimeException ( " Erreur
aleatoire !") ;
7 return 42;
8 })
Concurrency & Multithreading en Java Dr. BADR EL KHALYLY
CHAPITRE 3. PROGRAMMATION ASYNCHRONE NON-BLOQUANTE 27
9 // exceptionally : gere l ' erreur , retourne valeur par
defaut
10 . exceptionally ( ex -> {
11 System . err . println (" Erreur : " + ex . getMessage () ) ;
12 return -1;
13 })
14 // handle : gere succes ET erreur
15 . handle (( result , ex ) -> {
16 if ( ex != null ) return 0;
17 return result * 2;
18 }) ;
19
20 System . out . println (" Resultat : " + cf . join () ) ;
21 }
22 }
Concurrency & Multithreading en Java Dr. BADR EL KHALYLY
CHAPITRE 3. PROGRAMMATION ASYNCHRONE NON-BLOQUANTE 28
3.3 Exemple complet : Usine de Chaussures
Figure 3.2 Diagramme de séquence : Usine de chaussures avec CompletableFuture
Listing 3.6 Usine de Chaussures 5 Machines, 70 Chaussures
1 import java . util . concurrent .*;
2 import java . util .*;
3 import java . util . stream .*;
4
5 public class UsineChaussures {
6 static final int NB_MACHINES = 5;
7 static final int TOTAL_CHAUSSURES = 70;
8 static final int PAR_MACHINE = TOTAL_CHAUSSURES / NB_MACHINES ;
// 14
9
10 public static void main ( String [] args ) {
11 ExecutorService executor =
Executors . newFixedThreadPool ( NB_MACHINES );
Concurrency & Multithreading en Java Dr. BADR EL KHALYLY
CHAPITRE 3. PROGRAMMATION ASYNCHRONE NON-BLOQUANTE 29
12 long debut = System . currentTimeMillis () ;
13
14 System . out . println (" === USINE DE CHAUSSURES === " );
15 System . out . println (" Machines : " + NB_MACHINES );
16 System . out . println (" Total a produire : " +
TOTAL_CHAUSSURES );
17 System . out . println (" Par machine : " + PAR_MACHINE );
18 System . out . println () ;
19
20 // Creer un CompletableFuture par machine
21 List < CompletableFuture < List < String >> > machines =
22 IntStream . rangeClosed (1 , NB_MACHINES )
23 . mapToObj ( machineId ->
24 CompletableFuture . supplyAsync (
25 () -> produire ( machineId , PAR_MACHINE ) ,
executor )
26 . thenApply ( chaussures ->
27 controleQualite ( machineId , chaussures ))
28 )
29 . collect ( Collectors . toList () ) ;
30
31 // Attendre TOUTES les machines ( non bloquant jusqu 'au
join )
32 CompletableFuture < Void > toutesTerminees = CompletableFuture
33 . allOf ( machines . toArray ( new CompletableFuture [0]) );
34
35 // Combiner tous les resultats
36 CompletableFuture < List < String > > resultatFinal =
toutesTerminees
37 . thenApply (v -> machines . stream ()
38 . flatMap ( m -> m. join () . stream () )
39 . collect ( Collectors . toList () )
40 );
41
42 // Rapport final
43 resultatFinal . thenAccept ( toutes -> {
44 long duree = System . currentTimeMillis () - debut ;
45 System . out . println ( "\n === RAPPORT FINAL === ");
46 System . out . println ( " Total produites : " +
toutes . size () ) ;
47 System . out . println ( " Temps total : " + duree + " ms ");
48 System . out . println ( " Si sequentiel : ~ " +
49 ( TOTAL_CHAUSSURES * 100) + " ms ");
50 System . out . println ( " Acceleration : ~ " +
51 String . format (" %.1 fx " ,
52 ( TOTAL_CHAUSSURES * 100.0) / duree ) );
53 }) . join () ; // Attend la fin du pipeline
54
55 executor . shutdown () ;
56 }
57
Concurrency & Multithreading en Java Dr. BADR EL KHALYLY
CHAPITRE 3. PROGRAMMATION ASYNCHRONE NON-BLOQUANTE 30
58 static List < String > produire ( int machineId , int quantite ) {
59 System . out . println (" [ Machine " + machineId +
60 "] Debut production de " + quantite + " paires " );
61 List < String > chaussures = new ArrayList < >() ;
62
63 for ( int i = 1; i <= quantite ; i ++) {
64 try { Thread . sleep (100) ; } // Simule le temps de
fabrication
65 catch ( InterruptedException e) {
66 Thread . currentThread () . interrupt () ; return
chaussures ;
67 }
68 String chaussure = "M " + machineId + " -P " + i;
69 chaussures . add ( chaussure ) ;
70
71 if ( i % 5 == 0 || i == quantite ) {
72 System . out . println ("[ Machine " + machineId +
73 " ] Progression : " + i + "/" + quantite );
74 }
75 }
76
77 System . out . println (" [ Machine " + machineId +
78 "] Production TERMINEE !" );
79 return chaussures ;
80 }
81
82 static List < String > controleQualite ( int machineId ,
83 List < String > chaussures ) {
84 System . out . println (" [ QC Machine " + machineId +
85 "] Controle de " + chaussures . size () + " paires ... ");
86 try { Thread . sleep (200) ; } // Simule le controle
87 catch ( InterruptedException e) {
88 Thread . currentThread () . interrupt () ;
89 }
90
91 // Simule un taux de rejet de 5%
92 List < String > validees = chaussures . stream ()
93 . filter (c -> Math . random () > 0.05)
94 . collect ( Collectors . toList () ) ;
95
96 int rejetees = chaussures . size () - validees . size () ;
97 System . out . println (" [ QC Machine " + machineId +
98 "] Validees : " + validees . size () +
99 " , Rejetees : " + rejetees );
100 return validees ;
101 }
102 }
Concurrency & Multithreading en Java Dr. BADR EL KHALYLY
CHAPITRE 3. PROGRAMMATION ASYNCHRONE NON-BLOQUANTE 31
3.4 Flow API (Java 9+) Reactive Streams
Listing 3.7 Flow API : Reactive Streams en Java
1 import java . util . concurrent .*;
2
3 public class FlowAPIExample {
4 public static void main ( String [] args ) throws
InterruptedException {
5 // Publisher : source de donnees
6 SubmissionPublisher < String > publisher = new
SubmissionPublisher < >() ;
7
8 // Subscriber : consommateur reactif
9 Flow . Subscriber < String > subscriber = new
Flow . Subscriber < >() {
10 private Flow . Subscription subscription ;
11
12 @Override
13 public void onSubscribe ( Flow . Subscription
subscription ) {
14 this . subscription = subscription ;
15 subscription . request (1) ; // Demande 1 element
( backpressure )
16 }
17
18 @Override
19 public void onNext ( String item ) {
20 System . out . println (" Recu : " + item + " [" +
21 Thread . currentThread () . getName () + "] ");
22 subscription . request (1) ; // Demande le suivant
23 }
24
25 @Override
26 public void onError ( Throwable throwable ) {
27 System . err . println (" Erreur : " +
throwable . getMessage () );
28 }
29
30 @Override
31 public void onComplete () {
32 System . out . println (" Flux termine !" );
33 }
34 };
35
36 publisher . subscribe ( subscriber );
37
38 // Publier des elements
39 for ( int i = 1; i <= 5; i ++) {
40 publisher . submit (" Message " + i );
41 }
Concurrency & Multithreading en Java Dr. BADR EL KHALYLY
CHAPITRE 3. PROGRAMMATION ASYNCHRONE NON-BLOQUANTE 32
42
43 publisher . close () ; // Declenche onComplete
44 Thread . sleep (1000) ; // Laisse le temps au traitement async
45 }
46 }
3.4.1 Récapitulatif
Récapitulatif du chapitre
La programmation asynchrone non-bloquante libère les threads pendant les
attentes I/O
CompletableFuture : le pilier de l'asynchrone en Java
supplyAsync/runAsync : lancer une tâche asynchrone
thenApply/thenCompose/thenAccept : chaîner les opérations
allOf/anyOf : combiner plusieurs futures
exceptionally/handle : gérer les erreurs
Flow API (Java 9+) : reactive streams avec backpressure
L'exemple de l'usine de chaussures montre la puissance du parallélisme asyn-
chrone : 5 machines produisent 70 chaussures en ∼1/5 du temps séquentiel
3.4.2 Exercices du Chapitre 3
Exercice: 3.1 API Météo asynchrone
Simulez un service météo asynchrone qui :
1. Appelle 3 APIs en parallèle : température, humidité, vent
2. Combine les résultats avec thenCombine
3. Ajoute un timeout de 5 secondes
4. Gère les erreurs avec exceptionally
5. Retourne un objet Meteo combiné
Concurrency & Multithreading en Java Dr. BADR EL KHALYLY
Chapitre 4
Exclusion Mutuelle et Synchronisation
4.1 Le problème de la Race Condition
Problématique: Race Condition
Lorsque plusieurs threads accèdent et modient une ressource partagée simulta-
nément sans synchronisation, le résultat est imprévisible et souvent incorrect.
C'est la race condition (condition de course).
33
CHAPITRE 4. EXCLUSION MUTUELLE ET SYNCHRONISATION 34
Figure 4.1 Diagramme de séquence : Race Condition sans synchronisation
Listing 4.1 Démonstration d'une Race Condition
1 public class RaceConditionDemo {
2 private static int compteur = 0;
Concurrency & Multithreading en Java Dr. BADR EL KHALYLY
CHAPITRE 4. EXCLUSION MUTUELLE ET SYNCHRONISATION 35
3
4 public static void main ( String [] args ) throws
InterruptedException {
5 Runnable incrementer = () -> {
6 for ( int i = 0; i < 100000; i ++) {
7 compteur ++; // Operation NON atomique !
8 // Equivalent a: read -> increment -> write
9 }
10 };
11
12 Thread t1 = new Thread ( incrementer );
13 Thread t2 = new Thread ( incrementer );
14 t1 . start () ;
15 t2 . start () ;
16 t1 . join () ;
17 t2 . join () ;
18
19 // Attendu : 200000 , Obtenu : valeur aleatoire < 200000
20 System . out . println (" Compteur : " + compteur );
21 }
22 }
Concurrency & Multithreading en Java Dr. BADR EL KHALYLY
CHAPITRE 4. EXCLUSION MUTUELLE ET SYNCHRONISATION 36
Concurrency & Multithreading en Java Dr. BADR EL KHALYLY
CHAPITRE 4. EXCLUSION MUTUELLE ET SYNCHRONISATION 37
4.2 Le mot-clé synchronized
Figure 4.2 Diagramme de séquence : Synchronisation avec synchronized
Concurrency & Multithreading en Java Dr. BADR EL KHALYLY
CHAPITRE 4. EXCLUSION MUTUELLE ET SYNCHRONISATION 38
4.2.1 Méthode synchronized
Listing 4.2 Utilisation de synchronized sur une méthode
1 public class CompteurSynchronise {
2 private int compteur = 0;
3
4 // Un seul thread a la fois peut executer cette methode
5 public synchronized void incrementer () {
6 compteur ++; // Maintenant thread - safe
7 }
8
9 public synchronized int getCompteur () {
10 return compteur ;
11 }
12
13 public static void main ( String [] args ) throws
InterruptedException {
14 CompteurSynchronise cs = new CompteurSynchronise () ;
15
16 Runnable tache = () -> {
17 for ( int i = 0; i < 100000; i ++) {
18 cs . incrementer () ;
19 }
20 };
21
22 Thread t1 = new Thread ( tache ) ;
23 Thread t2 = new Thread ( tache ) ;
24 t1 . start () ; t2 . start () ;
25 t1 . join () ; t2 . join () ;
26
27 System . out . println (" Compteur : " + cs . getCompteur () ) ;
28 // Toujours 200000!
29 }
30 }
4.2.2 Bloc synchronized
Listing 4.3 Bloc synchronized pour un contrôle plus n
1 public class BlocSynchronise {
2 private final Object verrou = new Object () ;
3 private int solde = 1000;
4
5 public void deposer ( int montant ) {
6 // Code non critique ici ...
7 synchronized ( verrou ) { // Seule la section critique est
verrouillee
8 solde += montant ;
9 }
10 }
Concurrency & Multithreading en Java Dr. BADR EL KHALYLY
CHAPITRE 4. EXCLUSION MUTUELLE ET SYNCHRONISATION 39
11
12 public void retirer ( int montant ) {
13 synchronized ( verrou ) {
14 if ( solde >= montant ) {
15 solde -= montant ;
16 }
17 }
18 }
19 }
4.3 [Link]
4.3.1 ReentrantLock
Listing 4.4 Utilisation de ReentrantLock
1 import java . util . concurrent . locks . ReentrantLock ;
2
3 public class CompteurAvecLock {
4 private final ReentrantLock lock = new ReentrantLock () ;
5 private int compteur = 0;
6
7 public void incrementer () {
8 lock . lock () ; // Acquiert le verrou
9 try {
10 compteur ++;
11 } finally {
12 lock . unlock () ; // TOUJOURS liberer dans finally !
13 }
14 }
15
16 public boolean tryIncrementer () {
17 if ( lock . tryLock () ) { // Tentative non bloquante
18 try {
19 compteur ++;
20 return true ;
21 } finally {
22 lock . unlock () ;
23 }
24 }
25 return false ; // Verrou non disponible
26 }
27 }
4.3.2 ReadWriteLock
Listing 4.5 ReadWriteLock pour lectures concurrentes
1 import java . util . concurrent . locks .*;
Concurrency & Multithreading en Java Dr. BADR EL KHALYLY
CHAPITRE 4. EXCLUSION MUTUELLE ET SYNCHRONISATION 40
2
3 public class CacheAvecRWLock {
4 private final ReadWriteLock rwLock = new
ReentrantReadWriteLock () ;
5 private final Lock readLock = rwLock . readLock () ;
6 private final Lock writeLock = rwLock . writeLock () ;
7 private String data = "" ;
8
9 public String lire () {
10 readLock . lock () ; // Plusieurs lecteurs simultanement
11 try {
12 return data ;
13 } finally {
14 readLock . unlock () ;
15 }
16 }
17
18 public void ecrire ( String newData ) {
19 writeLock . lock () ; // Exclusif : aucun lecteur ni ecrivain
20 try {
21 data = newData ;
22 } finally {
23 writeLock . unlock () ;
24 }
25 }
26 }
Problématique: Deadlock
Un deadlock survient quand deux threads s'attendent mutuellement, chacun dé-
tenant un verrou dont l'autre a besoin :
1 // Thread 1: // Thread 2:
2 lock (A) ; lock (B) ;
3 lock (B) ; // BLOQUE lock ( A); // BLOQUE
4 // -> DEADLOCK ! // -> DEADLOCK !
Solutions : toujours acquérir les verrous dans le même ordre, utiliser tryLock()
avec timeout.
4.3.3 Récapitulatif
Récapitulatif du chapitre
synchronized : mécanisme intégré de Java, simple à utiliser
ReentrantLock : plus exible (tryLock, timeout, fairness)
ReadWriteLock : optimise les scénarios lecture-intensive
Toujours libérer les verrous dans un bloc finally
Attention au deadlock : acquérir les verrous dans un ordre cohérent
Concurrency & Multithreading en Java Dr. BADR EL KHALYLY
CHAPITRE 4. EXCLUSION MUTUELLE ET SYNCHRONISATION 41
4.3.4 Exercices du Chapitre 3
Exercice: 3.1 Compte bancaire thread-safe
Implémentez un système bancaire avec :
1. Une classe CompteBancaire avec deposer(), retirer(), getSolde()
2. 10 threads qui eectuent des dépôts et retraits simultanés
3. Vériez que le solde nal est correct avec et sans synchronisation
4. Implémentez un transfert atomique entre deux comptes (attention au dead-
lock !)
Exercice: 3.2 Détection de deadlock
Écrivez un programme qui crée intentionnellement un deadlock entre deux threads.
Puis modiez-le pour éviter le deadlock en utilisant tryLock() avec un timeout.
Concurrency & Multithreading en Java Dr. BADR EL KHALYLY
Chapitre 5
Visibilité et Modèle Mémoire Java
5.1 Le problème de visibilité
Problématique: Visibilité mémoire
En Java, chaque thread peut avoir une copie locale (cache CPU) des variables par-
tagées. Sans mécanisme de visibilité, un thread peut ne jamais voir les modications
eectuées par un autre thread, même après qu'elles aient été écrites.
Listing 5.1 Problème de visibilité
1 public class VisibiliteProbleme {
2 private static boolean running = true ; // Pas volatile !
3
4 public static void main ( String [] args ) throws
InterruptedException {
5 Thread worker = new Thread (() -> {
6 int count = 0;
7 while ( running ) { // Peut lire une copie cached !
8 count ++;
9 }
10 System . out . println ( " Arrete apres " + count + "
iterations ") ;
11 }) ;
12
13 worker . start () ;
14 Thread . sleep (1000) ;
15 running = false ; // Le worker peut NE JAMAIS voir ce
changement !
16 System . out . println (" Signal d ' arret envoye ");
17 }
18 }
42
CHAPITRE 5. VISIBILITÉ ET MODÈLE MÉMOIRE JAVA 43
5.2 Le mot-clé volatile
Dénition: volatile
Le mot-clé volatile garantit que :
Chaque lecture de la variable lit directement depuis la mémoire principale
Chaque écriture est immédiatement visible par tous les threads
Empêche la réorganisation des instructions (memory barrier)
Listing 5.2 Correction avec volatile
1 public class VisibiliteCorrigee {
2 private static volatile boolean running = true ; // volatile !
3
4 public static void main ( String [] args ) throws
InterruptedException {
5 Thread worker = new Thread (() -> {
6 int count = 0;
7 while ( running ) { // Lit TOUJOURS la valeur reelle
8 count ++;
9 }
10 System . out . println ( " Arrete apres " + count + "
iterations ") ;
11 }) ;
12
13 worker . start () ;
14 Thread . sleep (1000) ;
15 running = false ; // Visible immediatement par le worker
16 worker . join () ;
17 }
18 }
Note: Limitation de volatile
volatile garantit la visibilité mais PAS l'atomicité. L'opération counter++ sur
une variable volatile n'est PAS thread-safe car c'est une opération composée (read-
modify-write). Pour l'atomicité, utilisez synchronized ou les classes Atomic.
5.3 Java Memory Model (JMM)
Le Java Memory Model dénit les règles de visibilité entre threads :
Happens-before : si une action A happens-before B, alors les eets de A sont
visibles par B
Règles happens-before : synchronized unlock → lock suivant, écriture volatile
→ [Link]() →
lecture suivante, première action du thread, dernière action
du thread → join() retourne
Concurrency & Multithreading en Java Dr. BADR EL KHALYLY
CHAPITRE 5. VISIBILITÉ ET MODÈLE MÉMOIRE JAVA 44
5.4 Classes Atomiques
Listing 5.3 Utilisation des classes Atomiques
1 import java . util . concurrent . atomic .*;
2
3 public class AtomicExample {
4 private AtomicInteger compteur = new AtomicInteger (0) ;
5 private AtomicLong total = new AtomicLong (0) ;
6 private AtomicBoolean actif = new AtomicBoolean ( true ) ;
7 private AtomicReference < String > nom = new
AtomicReference < >( " initial ");
8
9 public void incrementer () {
10 compteur . incrementAndGet () ; // Atomique et thread - safe
11 }
12
13 public void ajouterSiPositif ( int valeur ) {
14 // Compare - And - Swap : mise a jour conditionnelle atomique
15 compteur . updateAndGet ( current ->
16 current + valeur > 0 ? current + valeur : current
17 );
18 }
19
20 public void updateNom ( String expected , String newVal ) {
21 // CAS : change seulement si la valeur actuelle est expected
22 nom . compareAndSet ( expected , newVal );
23 }
24 }
5.4.1 Récapitulatif
Récapitulatif du chapitre
volatile : garantit la visibilité mais pas l'atomicité
Le JMM dénit les relations happens-before entre actions de threads
Classes Atomic* : opérations atomiques sans verrou (lock-free) via CAS
AtomicInteger, AtomicLong, AtomicBoolean, AtomicReference
Préférer les classes atomiques pour les compteurs et ags simples
Concurrency & Multithreading en Java Dr. BADR EL KHALYLY
CHAPITRE 5. VISIBILITÉ ET MODÈLE MÉMOIRE JAVA 45
5.4.2 Exercices du Chapitre 4
Exercice: 4.1 Compteur de visites
Implémentez un compteur de visites web thread-safe de 3 manières :
1. Avec synchronized
2. Avec AtomicInteger
3. Avec volatile seul (montrez pourquoi ça ne fonctionne pas pour l'incrémen-
tation)
Comparez les performances avec 10 threads faisant chacun 1 million d'incrémenta-
tions.
Concurrency & Multithreading en Java Dr. BADR EL KHALYLY
Chapitre 6
Atomicité et mécanisme CAS
6.1 Qu'est-ce que l'atomicité ?
Dénition: Atomicité
Une opération est atomique si elle s'exécute entièrement ou pas du tout, sans possi-
bilité d'état intermédiaire visible par un autre thread. En Java, les lectures/écritures
de types primitifs (sauf long et double) sont atomiques, mais les opérations com-
posées comme i++ ne le sont pas.
Problématique: Opérations non atomiques
L'opération i++ semble simple mais se décompose en 3 étapes :
1. Lire la valeur actuelle de i
2. Incrémenter la valeur en local
3. Écrire la nouvelle valeur
Entre ces étapes, un autre thread peut intervenir, causant une race condition.
6.2 Le mécanisme Compare-And-Swap (CAS)
Dénition: CAS Compare-And-Swap
Le CAS est une instruction processeur atomique qui :
1. Lit la valeur actuelle en mémoire
2. Compare avec la valeur attendue
3. Si identique, écrit la nouvelle valeur (atomiquement)
4. Si diérente, échoue et retente (spin loop)
C'est la base de toutes les classes [Link].
Listing 6.1 Implémentation conceptuelle du CAS
1 // Pseudo - code du CAS ( en realite c ' est une instruction CPU )
2 public class CASConcept {
46
CHAPITRE 6. ATOMICITÉ ET MÉCANISME CAS 47
3 private volatile int value ;
4
5 // Cette methode est executee atomiquement par le CPU
6 public synchronized boolean compareAndSwap ( int expected , int
newVal ) {
7 if ( value == expected ) {
8 value = newVal ;
9 return true ; // Succes
10 }
11 return false ; // Echec : quelqu 'un a modifie entre - temps
12 }
13
14 // Increment atomique avec CAS ( boucle de retry )
15 public void increment () {
16 int current ;
17 do {
18 current = value ;
19 } while (! compareAndSwap ( current , current + 1) );
20 }
21 }
6.3 [Link] en détail
Listing 6.2 LongAdder pour haute contention
1 import java . util . concurrent . atomic . LongAdder ;
2
3 public class LongAdderExample {
4 // LongAdder est plus performant qu ' AtomicLong
5 // sous haute contention ( beaucoup de threads )
6 private LongAdder compteur = new LongAdder () ;
7
8 public void incrementer () {
9 compteur . increment () ; // Distribue les mises a jour
10 }
11
12 public long getTotal () {
13 return compteur . sum () ; // Agrege tous les compteurs
internes
14 }
15 }
Concurrency & Multithreading en Java Dr. BADR EL KHALYLY
CHAPITRE 6. ATOMICITÉ ET MÉCANISME CAS 48
6.3.1 Récapitulatif
Récapitulatif du chapitre
L'atomicité garantit qu'une opération est indivisible
Le CAS est une instruction CPU atomique (comparer et échanger)
Les classes Atomic* utilisent le CAS en interne (lock-free)
LongAdder/LongAccumulator : optimisés pour la haute contention
Lock-free ̸= wait-free : le CAS peut retenter en boucle
6.3.2 Exercices du Chapitre 5
Exercice: 5.1 Implémentation d'un Stack lock-free
Implémentez une pile (Stack) thread-safe en utilisant AtomicReference et le pat-
tern CAS (sans synchronized ni Lock). La pile doit supporter push() et pop()
atomiques.
Concurrency & Multithreading en Java Dr. BADR EL KHALYLY
Chapitre 7
Immutabilité
7.1 Pourquoi l'immutabilité ?
Problématique: Complexité de la synchronisation
Synchroniser correctement les accès aux objets mutables est dicile et source de
pas besoin de synchronisation ? Un objet im-
bugs. Et si on n'avait simplement
muable (dont l'état ne peut pas changer après création) est intrinsèquement
thread-safe sans aucun mécanisme de synchronisation.
Dénition: Objet Immuable
Un objet est immuable si son état ne peut pas être modié après sa construction.
Conditions :
Tous les champs sont final
Pas de setters
Pas de fuite de références vers des objets mutables internes
La classe est final (ne peut pas être sous-classée)
Listing 7.1 Classe immuable en Java
1 public final class Personne { // final : non extensible
2 private final String nom ; // final : non modifiable
3 private final int age ;
4 private final List < String > hobbies ;
5
6 public Personne ( String nom , int age , List < String > hobbies ) {
7 this . nom = nom ;
8 this . age = age ;
9 // Copie defensive pour eviter la fuite de reference
10 this . hobbies = List . copyOf ( hobbies ); // Java 10+
11 }
12
13 public String getNom () { return nom ; }
14 public int getAge () { return age ; }
15 public List < String > getHobbies () {
49
CHAPITRE 7. IMMUTABILITÉ 50
16 return hobbies ; // Deja immuable grace a List . copyOf
17 }
18 }
7.1.1 Records Java (Java 16+)
Listing 7.2 Records : immutabilité simpliée
1 // Un record est immuable par defaut
2 public record Point ( double x , double y) {
3 // Validation dans le constructeur compact
4 public Point {
5 if ( Double . isNaN (x ) || Double . isNaN (y) ) {
6 throw new IllegalArgumentException (" Coordonnees
invalides ");
7 }
8 }
9
10 public double distance ( Point other ) {
11 return Math . sqrt ( Math . pow (x - other .x , 2) +
12 Math . pow ( y - other .y , 2) );
13 }
14 }
15 // Usage : Point p = new Point (3.0 , 4.0) ;
16 // p. x () -> 3.0 ( pas de setter !)
7.1.2 Collections immuables
Listing 7.3 Collections immuables en Java
1 import java . util .*;
2
3 public class CollectionsImmuables {
4 public static void main ( String [] args ) {
5 // Java 9+: factory methods
6 List < String > liste = List . of ( "A" , "B " , "C") ;
7 Set < Integer > ensemble = Set . of (1 , 2, 3) ;
8 Map < String , Integer > map = Map . of (" un " , 1, " deux " , 2) ;
9
10 // liste . add (" D ") ; -> UnsupportedOperationException !
11
12 // Copie immuable d ' une collection existante ( Java 10+)
13 List < String > mutable = new ArrayList < >( List . of (" X" , "Y" ));
14 List < String > immuable = List . copyOf ( mutable ) ;
15
16 // Collections . unmodifiableList ( vue non modifiable )
17 List < String > vue = Collections . unmodifiableList ( mutable );
18 }
19 }
Concurrency & Multithreading en Java Dr. BADR EL KHALYLY
CHAPITRE 7. IMMUTABILITÉ 51
7.1.3 Récapitulatif
Récapitulatif du chapitre
Les objets immuables sont intrinsèquement thread-safe
Conditions : champs final, pas de setters, copies défensives, classe final
record (Java 16+) : syntaxe concise pour les classes immuables
[Link](), [Link](), [Link]() : collections immuables (Java 9+)
Favoriser l'immutabilité réduit considérablement les bugs de concurrence
7.1.4 Exercices du Chapitre 6
Exercice: 6.1 Conception immuable
Concevez une classe immuable Transaction avec : id (UUID), montant, devise,
date, description. Créez un record équivalent. Vériez la thread-safety en parta-
geant des instances entre 10 threads.
Concurrency & Multithreading en Java Dr. BADR EL KHALYLY
Chapitre 8
Coordination entre Threads
8.1 wait(), notify() et notifyAll()
Problématique: Communication entre threads
Comment un thread peut-il signaler à un autre thread qu'une condition est remplie
(ex : donnée disponible, espace libre dans un buer) ? L'attente active (busy-waiting )
gaspille le CPU. Les mécanismes wait/notify permettent une coordination ecace.
52
CHAPITRE 8. COORDINATION ENTRE THREADS 53
Concurrency & Multithreading en Java Dr. BADR EL KHALYLY
Figure 8.1 Diagramme de séquence : wait() et notify()
CHAPITRE 8. COORDINATION ENTRE THREADS 54
Listing 8.1 Producteur-Consommateur avec wait/notify
1 import java . util . LinkedList ;
2 import java . util . Queue ;
3
4 public class ProducteurConsommateur {
5 private final Queue < Integer > buffer = new LinkedList < >() ;
6 private final int CAPACITE = 5;
7
8 public synchronized void produire ( int item )
9 throws InterruptedException {
10 while ( buffer . size () == CAPACITE ) {
11 System . out . println ( " Buffer plein , producteur
attend ... ") ;
12 wait () ; // Libere le verrou et attend
13 }
14 buffer . add ( item );
15 System . out . println (" Produit : " + item +
16 " ( taille : " + buffer . size () + ") ");
17 notifyAll () ; // Reveille les consommateurs en attente
18 }
19
20 public synchronized int consommer () throws
InterruptedException {
21 while ( buffer . isEmpty () ) {
22 System . out . println ( " Buffer vide , consommateur
attend ... ") ;
23 wait () ;
24 }
25 int item = buffer . poll () ;
26 System . out . println (" Consomme : " + item +
27 " ( taille : " + buffer . size () + ") ");
28 notifyAll () ; // Reveille les producteurs en attente
29 return item ;
30 }
31 }
8.2 Outils [Link]
8.2.1 CountDownLatch
Listing 8.2 CountDownLatch : attendre N événements
1 import java . util . concurrent . CountDownLatch ;
2
3 public class CountDownLatchExample {
4 public static void main ( String [] args ) throws
InterruptedException {
5 int nbServices = 3;
6 CountDownLatch latch = new CountDownLatch ( nbServices );
7
Concurrency & Multithreading en Java Dr. BADR EL KHALYLY
CHAPITRE 8. COORDINATION ENTRE THREADS 55
8 for ( int i = 1; i <= nbServices ; i ++) {
9 final int serviceId = i;
10 new Thread (() -> {
11 System . out . println (" Service " + serviceId +
12 " demarre ... ") ;
13 try { Thread . sleep (( long )( Math . random () * 3000) ); }
14 catch ( InterruptedException e ) { return ; }
15 System . out . println (" Service " + serviceId + "
PRET " );
16 latch . countDown () ; // Decremente le compteur
17 }) . start () ;
18 }
19
20 System . out . println (" En attente de tous les services ... " );
21 latch . await () ; // Bloque jusqu 'a ce que compteur = 0
22 System . out . println (" Tous les services sont prets !") ;
23 }
24 }
8.2.2 CyclicBarrier
Listing 8.3 CyclicBarrier : synchronisation par phases
1 import java . util . concurrent . CyclicBarrier ;
2
3 public class CyclicBarrierExample {
4 public static void main ( String [] args ) {
5 int nbJoueurs = 3;
6 CyclicBarrier barrier = new CyclicBarrier ( nbJoueurs ,
7 () -> System . out . println (" === Tous prets , GO ! === ") );
8
9 for ( int i = 1; i <= nbJoueurs ; i ++) {
10 final int joueurId = i ;
11 new Thread (() -> {
12 try {
13 System . out . println (" Joueur " + joueurId +
14 " se prepare ... ") ;
15 Thread . sleep (( long )( Math . random () * 2000) );
16 System . out . println (" Joueur " + joueurId +
17 " pret , attend les autres ");
18 barrier . await () ; // Attend que tous arrivent
19 System . out . println (" Joueur " + joueurId +
20 " commence la course ! ");
21 } catch ( Exception e ) { e. printStackTrace () ; }
22 }) . start () ;
23 }
24 }
25 }
Concurrency & Multithreading en Java Dr. BADR EL KHALYLY
CHAPITRE 8. COORDINATION ENTRE THREADS 56
8.2.3 Semaphore
Listing 8.4 Semaphore : limiter l'accès concurrent
1 import java . util . concurrent . Semaphore ;
2
3 public class SemaphoreExample {
4 // Parking avec 3 places
5 private static final Semaphore parking = new Semaphore (3) ;
6
7 public static void main ( String [] args ) {
8 for ( int i = 1; i <= 8; i ++) {
9 final int voitureId = i;
10 new Thread (() -> {
11 try {
12 System . out . println (" Voiture " + voitureId +
13 " cherche une place ... ") ;
14 parking . acquire () ; // Bloque si pas de place
15 System . out . println (" Voiture " + voitureId +
16 " GAREE ( places restantes : " +
17 parking . availablePermits () + ")" );
18 Thread . sleep (2000) ; // Reste garee 2s
19 System . out . println (" Voiture " + voitureId +
20 " PART " );
21 } catch ( InterruptedException e) {
22 return ;
23 } finally {
24 parking . release () ; // Libere la place
25 }
26 }) . start () ;
27 }
28 }
29 }
8.2.4 BlockingQueue
Listing 8.5 BlockingQueue : Producteur-Consommateur simplié
1 import java . util . concurrent .*;
2
3 public class BlockingQueueExample {
4 public static void main ( String [] args ) {
5 BlockingQueue < String > queue = new ArrayBlockingQueue < >(5) ;
6
7 // Producteur
8 new Thread (() -> {
9 try {
10 for ( int i = 1; i <= 10; i ++) {
11 String item = " Item -" + i;
12 queue . put ( item ); // Bloque si queue pleine
13 System . out . println (" Produit : " + item );
Concurrency & Multithreading en Java Dr. BADR EL KHALYLY
CHAPITRE 8. COORDINATION ENTRE THREADS 57
14 }
15 queue . put (" FIN " ); // Signal d ' arret
16 } catch ( InterruptedException e ) { return ; }
17 }) . start () ;
18
19 // Consommateur
20 new Thread (() -> {
21 try {
22 while ( true ) {
23 String item = queue . take () ; // Bloque si vide
24 if (" FIN ". equals ( item )) break ;
25 System . out . println (" Consomme : " + item );
26 Thread . sleep (500) ;
27 }
28 } catch ( InterruptedException e ) { return ; }
29 }) . start () ;
30 }
31 }
8.2.5 Récapitulatif
Récapitulatif du chapitre
wait/notify : coordination de bas niveau (toujours dans un bloc
synchronized)
CountDownLatch : attendre que N événements se produisent (usage unique)
CyclicBarrier : point de rendez-vous réutilisable pour N threads
Semaphore : limiter le nombre d'accès concurrents à une ressource
BlockingQueue : le d'attente thread-safe avec blocage automatique
Préférer les outils [Link] aux wait/notify bruts
8.2.6 Exercices du Chapitre 7
Exercice: 7.1 Simulation de restaurant
Simulez un restaurant avec :
3 cuisiniers (producteurs) qui préparent des plats
5 serveurs (consommateurs) qui servent les plats
Un comptoir avec 10 places max (BlockingQueue)
Chaque plat prend un temps aléatoire de préparation
Exercice: 7.2 Barrière de synchronisation
Simulez un système de démarrage d'application :
1. 5 services doivent démarrer (base de données, cache, API, auth, logging)
Concurrency & Multithreading en Java Dr. BADR EL KHALYLY
CHAPITRE 8. COORDINATION ENTRE THREADS 58
2. Utilisez CountDownLatch pour attendre que tous soient prêts
3. Le service principal ne démarre que quand tous les autres sont prêts
Concurrency & Multithreading en Java Dr. BADR EL KHALYLY
Chapitre 9
Gestion des Tâches : Executor Frame-
work
9.1 Problématique
Problématique: Gestion manuelle des threads
Créer un new Thread() pour chaque tâche pose des problèmes :
Coût : créer/détruire des threads est coûteux (allocation stack, appel OS)
Saturation : trop de threads → OutOfMemoryError ou surcharge du sche-
duler
Pas de contrôle : impossible de limiter le nombre de threads actifs
Pas de résultat : [Link]() ne retourne rien et ne lance pas d'excep-
tion checked
9.2 Runnable vs Callable
Listing 9.1 Runnable vs Callable
1 // Runnable : pas de resultat , pas d ' exception checked
2 Runnable tache1 = () -> {
3 System . out . println ( " Tache sans resultat ");
4 };
5
6 // Callable : retourne un resultat , peut lancer une exception
7 Callable < Double > tache2 = () -> {
8 double resultat = performCalcul () ;
9 return resultat ; // Retourne un resultat
10 };
9.3 ExecutorService
59
CHAPITRE 9. GESTION DES TÂCHES: EXECUTOR FRAMEWORK 60
Listing 9.2 Les diérents types de pools
1 import java . util . concurrent .*;
2
3 public class ExecutorExamples {
4 public static void main ( String [] args ) throws Exception {
5 // Pool de taille fixe : N threads max
6 ExecutorService fixedPool =
Executors . newFixedThreadPool (4) ;
7
8 // Pool cache : cree des threads a la demande , reutilise
9 ExecutorService cachedPool =
Executors . newCachedThreadPool () ;
10
11 // Un seul thread : execution sequentielle garantie
12 ExecutorService singlePool =
Executors . newSingleThreadExecutor () ;
13
14 // Pool planifie : execution differee ou periodique
15 ScheduledExecutorService scheduled =
16 Executors . newScheduledThreadPool (2) ;
17
18 // Soumission de taches
19 fixedPool . submit (() -> System . out . println (" Tache
Runnable " ));
20
21 Future < Integer > future = fixedPool . submit (() -> {
22 Thread . sleep (1000) ;
23 return 42;
24 }) ;
25
26 System . out . println (" Resultat : " + future . get () ) ; //
Bloquant !
27
28 // TOUJOURS shutdown le pool
29 fixedPool . shutdown () ;
30 cachedPool . shutdown () ;
31 singlePool . shutdown () ;
32 scheduled . shutdown () ;
33 }
34 }
9.4 Future
Listing 9.3 Utilisation de Future
1 import java . util . concurrent .*;
2
3 public class FutureExample {
4 public static void main ( String [] args ) throws Exception {
5 ExecutorService executor = Executors . newFixedThreadPool (3) ;
Concurrency & Multithreading en Java Dr. BADR EL KHALYLY
CHAPITRE 9. GESTION DES TÂCHES : EXECUTOR FRAMEWORK 61
6
7 Future < String > f1 = executor . submit (() -> {
8 Thread . sleep (2000) ;
9 return " Resultat A";
10 }) ;
11
12 Future < String > f2 = executor . submit (() -> {
13 Thread . sleep (1000) ;
14 return " Resultat B";
15 }) ;
16
17 // Verifier si termine ( non bloquant )
18 System . out . println (" f1 termine ? " + f1 . isDone () ) ;
19
20 // Attendre avec timeout
21 try {
22 String r1 = f1 . get (3 , TimeUnit . SECONDS );
23 System . out . println ( r1 ) ;
24 } catch ( TimeoutException e) {
25 f1 . cancel ( true ); // Annuler si trop long
26 }
27
28 // Attendre indefiniment
29 String r2 = f2 . get () ; // BLOQUANT
30 System . out . println ( r2 );
31
32 executor . shutdown () ;
33 }
34 }
Problématique: Limitations de Future
Future a des limitations majeures :
get() bloquant pas d'exécution asynchrone de callback
est
Impossible de chaîner des opérations (thenApply, thenCompose)
Impossible de combiner plusieurs futures
Pas de gestion d'erreurs intégrée
La solution : CompletableFuture (Chapitre 10).
9.4.1 Récapitulatif
Récapitulatif du chapitre
Runnable : tâche sans résultat ; Callable<T> : tâche avec résultat
ExecutorService : pool de threads réutilisables
newFixedThreadPool(n) : nombre xe de threads
Future<T> : représente un résultat futur, get() bloquant
Concurrency & Multithreading en Java Dr. BADR EL KHALYLY
CHAPITRE 9. GESTION DES TÂCHES : EXECUTOR FRAMEWORK 62
Toujours appeler shutdown() sur les executors
9.4.2 Exercices du Chapitre 8
Exercice: 8.1 Téléchargeur parallèle
Implémentez un téléchargeur qui simule le téléchargement de 20 chiers en parallèle
avec un pool de 5 threads. Chaque téléchargement prend un temps aléatoire.
Utilisez Future pour collecter les résultats (taille du chier simulée). Achez le
temps total et le temps moyen par chier.
Concurrency & Multithreading en Java Dr. BADR EL KHALYLY
Chapitre 10
Parallélisme
10.1 Fork/Join Framework
Dénition: Fork/Join
Le framework Fork/Join (Java 7+) implémente le paradigme diviser pour régner
pour le parallélisme. Il décompose récursivement une tâche en sous-tâches jusqu'à ce
qu'elles soient assez petites, puis combine les résultats. Il utilise le work-stealing
pour équilibrer la charge entre les threads.
Listing 10.1 Fork/Join : somme d'un tableau
1 import java . util . concurrent .*;
2
3 public class SommeParallele extends RecursiveTask < Long > {
4 private final int [] tableau ;
5 private final int debut , fin ;
6 private static final int SEUIL = 1000;
7
8 public SommeParallele ( int [] tableau , int debut , int fin ) {
9 this . tableau = tableau ;
10 this . debut = debut ;
11 this . fin = fin ;
12 }
13
14 @Override
15 protected Long compute () {
16 if ( fin - debut <= SEUIL ) {
17 // Cas de base : calcul sequentiel
18 long somme = 0;
19 for ( int i = debut ; i < fin ; i ++) {
20 somme += tableau [i ];
21 }
22 return somme ;
23 }
24
25 // Diviser
26 int milieu = ( debut + fin ) / 2;
63
CHAPITRE 10. PARALLÉLISME 64
27 SommeParallele gauche = new SommeParallele ( tableau , debut ,
milieu );
28 SommeParallele droite = new SommeParallele ( tableau ,
milieu , fin ) ;
29
30 gauche . fork () ; // Soumet la sous - tache gauche
31 Long resultatDroit = droite . compute () ; // Calcule la droite
32 Long resultatGauche = gauche . join () ; // Attend la gauche
33
34 return resultatGauche + resultatDroit ; // Combiner
35 }
36
37 public static void main ( String [] args ) {
38 int [] data = new int [10 _000_000 ];
39 for ( int i = 0; i < data . length ; i ++) data [i] = i + 1;
40
41 ForkJoinPool pool = new ForkJoinPool () ;
42 long resultat = pool . invoke ( new SommeParallele ( data , 0,
data . length ));
43 System . out . println (" Somme : " + resultat );
44 }
45 }
10.2 Parallel Streams
Listing 10.2 Parallel Streams (Java 8+)
1 import java . util . stream .*;
2
3 public class ParallelStreamExample {
4 public static void main ( String [] args ) {
5 // Stream sequentiel vs parallele
6 long debut = System . currentTimeMillis () ;
7
8 long somme = LongStream . rangeClosed (1 , 100 _000_000 )
9 . parallel () // Active le parallelisme
10 . filter (n -> n % 2 == 0)
11 . sum () ;
12
13 long duree = System . currentTimeMillis () - debut ;
14 System . out . println (" Somme : " + somme + " en " + duree +
" ms ") ;
15
16 // Utilise ForkJoinPool . commonPool () en interne
17 System . out . println (" Parallelisme : " +
18 ForkJoinPool . commonPool () . getParallelism () );
19 }
20 }
Concurrency & Multithreading en Java Dr. BADR EL KHALYLY
CHAPITRE 10. PARALLÉLISME 65
Note: Quand utiliser le parallélisme ?
Le parallélisme n'est pas toujours bénéque :
Utilisez-le pour : gros volumes de données, opérations CPU-intensive, tâches
indépendantes
Évitez-le pour : petits volumes, opérations I/O, état partagé mutable, opé-
rations ordonnées
10.2.1 Récapitulatif
Récapitulatif du chapitre
Fork/Join : diviser-pour-régner parallèle avec work-stealing
RecursiveTask<V> : retourne un résultat ; RecursiveAction : pas de résultat
Parallel Streams : parallélisme déclaratif via .parallel()
Le parallélisme a un coût : ne l'utiliser que pour les tâches CPU-intensive
volumineuses
10.2.2 Exercices du Chapitre 9
Exercice: 9.1 Tri fusion parallèle
Implémentez un tri fusion (Merge Sort) parallèle avec le framework Fork/Join. Com-
parez les performances avec le tri séquentiel pour des tableaux de 1M, 10M et 50M
éléments.
Concurrency & Multithreading en Java Dr. BADR EL KHALYLY
Chapitre 11
Threads Virtuels (Project Loom Java
21+)
11.1 Problématique des Platform Threads
Problématique: Scalabilité des threads classiques
Les threads Java traditionnels ( platform threads) sont mappés 1 :1 avec les
threads du système d'exploitation. Chaque thread consomme environ 1 Mo de
stack. Avec 10 000 connexions concurrentes (serveur web), il faudrait 10 000 threads
≈ 10 Go de RAM juste pour les stacks ! C'est le problème C10K (gérer 10 000
connexions).
De plus, la majorité du temps, ces threads sont bloqués en attente d'I/O (base de
données, réseau, chier) un gaspillage énorme de ressources.
Table 11.1 Platform Thread vs Virtual Thread
Aspect Platform Thread Virtual Thread
Mapping OS 1 :1 (un thread OS par thread N :M (N virtuels sur M OS
Java) threads)
Mémoire stack ∼1 Mo xe Quelques Ko, grandit dynamique-
ment
Coût création Lourd (appel système) Très léger (objet Java)
Nombre max ∼milliers ∼millions
Blocage I/O Thread OS bloqué Virtual thread unmounted, carrier
libre
Scheduling OS scheduler JVM scheduler (ForkJoinPool)
66
CHAPITRE 11. THREADS VIRTUELS (PROJECT LOOM JAVA 21+) 67
11.2 Comment fonctionnent les Virtual Threads ?
Figure 11.1 Diagramme de séquence : Virtual Threads mount/unmount sur carrier
threads
Les virtual threads fonctionnent avec un mécanisme de mount/unmount :
1. Un virtual thread est monté sur un carrier thread (platform thread) pour s'exé-
cuter
Concurrency & Multithreading en Java Dr. BADR EL KHALYLY
CHAPITRE 11. THREADS VIRTUELS (PROJECT LOOM JAVA 21+) 68
2. Quand le virtual thread eectue un appel I/O bloquant, il est automatiquement
démonté (unmounted) du carrier thread
3. Le carrier thread est alors libre d'exécuter un autre virtual thread
4. Quand l'I/O est terminé, le virtual thread est remonté sur un carrier disponible
11.3 Utilisation en Java 21+
Listing 11.1 Création de Virtual Threads
1 public class VirtualThreadExample {
2 public static void main ( String [] args ) throws Exception {
3 // 1. Thread . startVirtualThread ( le plus simple )
4 Thread vt1 = Thread . startVirtualThread (() -> {
5 System . out . println ( " VT1 : " + Thread . currentThread () ) ;
6 System . out . println ( " Est virtuel : " +
7 Thread . currentThread () . isVirtual () );
8 }) ;
9 vt1 . join () ;
10
11 // 2. Thread . ofVirtual () . start ()
12 Thread vt2 = Thread . ofVirtual ()
13 . name (" mon - virtual - thread ")
14 . start (() -> {
15 System . out . println (" VT2 : " +
16 Thread . currentThread () . getName () );
17 }) ;
18 vt2 . join () ;
19
20 // 3. ExecutorService avec virtual threads
21 try ( var executor =
Executors . newVirtualThreadPerTaskExecutor () ) {
22 for ( int i = 0; i < 100 _000 ; i ++) {
23 final int taskId = i;
24 executor . submit (() -> {
25 // Chaque tache a son propre virtual thread
26 Thread . sleep (1000) ;
27 return " Tache " + taskId + " terminee ";
28 }) ;
29 }
30 } // Auto - shutdown avec try - with - resources
31 // 100 000 threads virtuels , seulement ~ N carrier threads !
32 }
33 }
Listing 11.2 Comparaison : 10 000 tâches I/O-bound
1 import java . time .*;
2 import java . util . concurrent .*;
3 import java . util . stream .*;
4
Concurrency & Multithreading en Java Dr. BADR EL KHALYLY
CHAPITRE 11. THREADS VIRTUELS (PROJECT LOOM JAVA 21+) 69
5 public class VirtualVsPlatform {
6 static final int NB_TACHES = 10 _000 ;
7
8 public static void main ( String [] args ) throws Exception {
9 // === Platform Threads ===
10 Instant debut1 = Instant . now () ;
11 try ( var executor = Executors . newFixedThreadPool (200) ) {
12 IntStream . range (0 , NB_TACHES ) . forEach (i ->
13 executor . submit (() -> {
14 Thread . sleep (1000) ; // Simule I /O
15 return i ;
16 })
17 );
18 }
19 Duration duree1 = Duration . between ( debut1 , Instant . now () ) ;
20 System . out . println (" Platform threads ( pool 200) : " +
21 duree1 . toSeconds () + "s" );
22 // ~50 secondes (10000/200 = 50 batches de 1s )
23
24 // === Virtual Threads ===
25 Instant debut2 = Instant . now () ;
26 try ( var executor =
Executors . newVirtualThreadPerTaskExecutor () ) {
27 IntStream . range (0 , NB_TACHES ) . forEach (i ->
28 executor . submit (() -> {
29 Thread . sleep (1000) ; // Simule I /O
30 return i ;
31 })
32 );
33 }
34 Duration duree2 = Duration . between ( debut2 , Instant . now () ) ;
35 System . out . println (" Virtual threads : " +
36 duree2 . toSeconds () + "s" );
37 // ~1 seconde ! Tous les 10000 dorment en parallele
38 }
39 }
Note: Quand utiliser les Virtual Threads ?
Oui : serveurs web, microservices, appels API, requêtes BD toute tâche
I/O-bound
Non : calculs CPU-intensifs (les virtual threads ne créent pas plus de c÷urs
CPU)
Attention : évitez synchronized avec I/O dans le bloc (le virtual thread ne
peut pas être unmonté utilisez ReentrantLock)
Concurrency & Multithreading en Java Dr. BADR EL KHALYLY
CHAPITRE 11. THREADS VIRTUELS (PROJECT LOOM JAVA 21+) 70
11.3.1 Récapitulatif
Récapitulatif du chapitre
Les virtual threads (Java 21+) résolvent le problème de scalabilité des
threads classiques
Mécanisme de mount/unmount : un virtual thread bloqué libère automati-
quement le carrier thread
Création : [Link](), [Link](), ou
newVirtualThreadPerTaskExecutor()
Permettent de créer des millions de threads avec une empreinte mémoire
minimale
Idéaux pour les applications I/O-bound (serveurs web, microservices)
Le code reste séquentiel et lisible tout en étant hautement concurrent
11.3.2 Exercices du Chapitre 11
Exercice: 11.1 Serveur web simulé
Simulez un serveur web qui reçoit 50 000 requêtes HTTP. Chaque requête prend
200ms d'I/O simulé. Implémentez avec :
1. Un pool xe de 100 platform threads
2. Des virtual threads (newVirtualThreadPerTaskExecutor)
Comparez les temps d'exécution et l'utilisation mémoire.
Concurrency & Multithreading en Java Dr. BADR EL KHALYLY
Chapitre 12
Exercices et Challenges Corrigés
12.1 Challenge 1 : Compteur thread-safe
Challenge: Compteur partagé
Implémentez un compteur partagé entre 10 threads. Chaque thread incrémente le
compteur 100 000 fois. Le résultat nal doit être exactement 1 000 000.
Solution :
Listing 12.1 Solution Challenge 1 : Compteur thread-safe
1 import java . util . concurrent . atomic . AtomicInteger ;
2
3 public class Challenge1 {
4 // Solution 1: synchronized
5 static int compteurSync = 0;
6 static synchronized void incrementSync () { compteurSync ++; }
7
8 // Solution 2: AtomicInteger ( recommande )
9 static AtomicInteger compteurAtomic = new AtomicInteger (0) ;
10
11 public static void main ( String [] args ) throws
InterruptedException {
12 int nbThreads = 10;
13 int nbIncrements = 100 _000 ;
14 Thread [] threads = new Thread [ nbThreads ];
15
16 // Test avec AtomicInteger
17 long debut = System . currentTimeMillis () ;
18 for ( int i = 0; i < nbThreads ; i ++) {
19 threads [i] = new Thread (() -> {
20 for ( int j = 0; j < nbIncrements ; j ++) {
21 compteurAtomic . incrementAndGet () ;
22 }
23 }) ;
24 threads [i ]. start () ;
25 }
26 for ( Thread t : threads ) t. join () ;
71
CHAPITRE 12. EXERCICES ET CHALLENGES CORRIGÉS 72
27 long duree = System . currentTimeMillis () - debut ;
28
29 System . out . println (" AtomicInteger : " + compteurAtomic . get ()
30 + " en " + duree + " ms ") ;
31 // Affiche : AtomicInteger : 1000000
32 }
33 }
12.2 Challenge 2 : Producteur-Consommateur
Challenge: Producteur-Consommateur avec BlockingQueue
Créez un système avec 3 producteurs et 2 consommateurs utilisant une
BlockingQueue. Les producteurs génèrent 100 éléments chacun. Les consomma-
teurs s'arrêtent quand un objet poison pill est reçu.
Listing 12.2 Solution Challenge 2 : Producteur-Consommateur
1 import java . util . concurrent .*;
2
3 public class Challenge2 {
4 static final String POISON = " STOP " ;
5
6 public static void main ( String [] args ) throws
InterruptedException {
7 BlockingQueue < String > queue = new ArrayBlockingQueue < >(20) ;
8 int nbProducteurs = 3;
9 int nbConsommateurs = 2;
10 int itemsParProducteur = 100;
11
12 // Lancer les producteurs
13 for ( int p = 1; p <= nbProducteurs ; p ++) {
14 final int prodId = p;
15 new Thread (() -> {
16 try {
17 for ( int i = 1; i <= itemsParProducteur ; i ++) {
18 String item = "P" + prodId + " - Item " + i ;
19 queue . put ( item ) ;
20 }
21 System . out . println (" Producteur " + prodId + "
termine " );
22 } catch ( InterruptedException e) {
23 Thread . currentThread () . interrupt () ;
24 }
25 }, " Producteur -" + p) . start () ;
26 }
27
28 // Thread qui envoie les poison pills apres les producteurs
29 new Thread (() -> {
30 try {
Concurrency & Multithreading en Java Dr. BADR EL KHALYLY
CHAPITRE 12. EXERCICES ET CHALLENGES CORRIGÉS 73
31 Thread . sleep (5000) ; // Attend que tout soit produit
32 for ( int i = 0; i < nbConsommateurs ; i ++) {
33 queue . put ( POISON ) ;
34 }
35 } catch ( InterruptedException e ) {
36 Thread . currentThread () . interrupt () ;
37 }
38 }) . start () ;
39
40 // Lancer les consommateurs
41 for ( int c = 1; c <= nbConsommateurs ; c ++) {
42 final int consId = c;
43 new Thread (() -> {
44 try {
45 int count = 0;
46 while ( true ) {
47 String item = queue . take () ;
48 if ( POISON . equals ( item )) {
49 System . out . println (" Consommateur " +
consId +
50 " arrete apres " + count + "
items " );
51 break ;
52 }
53 count ++;
54 }
55 } catch ( InterruptedException e) {
56 Thread . currentThread () . interrupt () ;
57 }
58 }, " Consommateur - " + c). start () ;
59 }
60 }
61 }
12.3 Challenge 3 : Pipeline asynchrone Completable-
Future
Challenge: Pipeline de traitement d'image simulé
Simulez un pipeline de traitement d'images avec CompletableFuture :
1. Charger 10 images (simulé, 500ms chacune)
2. Appliquer un ltre (300ms)
3. Redimensionner (200ms)
4. Sauvegarder (400ms)
Traitez toutes les images en parallèle et achez le temps total.
Concurrency & Multithreading en Java Dr. BADR EL KHALYLY
CHAPITRE 12. EXERCICES ET CHALLENGES CORRIGÉS 74
Listing 12.3 Solution Challenge 3 : Pipeline asynchrone
1 import java . util . concurrent .*;
2 import java . util .*;
3 import java . util . stream .*;
4
5 public class Challenge3 {
6 static ExecutorService executor =
Executors . newFixedThreadPool (4) ;
7
8 public static void main ( String [] args ) {
9 long debut = System . currentTimeMillis () ;
10
11 List < CompletableFuture < String > > pipelines =
12 IntStream . rangeClosed (1 , 10)
13 . mapToObj ( id ->
14 CompletableFuture
15 . supplyAsync (() -> charger ( " image " + id ) ,
executor )
16 . thenApplyAsync ( img -> filtrer ( img ) , executor )
17 . thenApplyAsync ( img -> redimensionner ( img ) ,
executor )
18 . thenApplyAsync ( img -> sauvegarder ( img ) ,
executor )
19 )
20 . collect ( Collectors . toList () ) ;
21
22 CompletableFuture . allOf (
23 pipelines . toArray ( new CompletableFuture [0]) )
24 . thenRun (() -> {
25 long duree = System . currentTimeMillis () - debut ;
26 System . out . println ( "\n === Toutes les images traitees
=== ") ;
27 System . out . println ( " Temps total : " + duree + " ms ");
28 System . out . println ( " Temps sequentiel : " +
29 (10 * (500 + 300 + 200 + 400) ) + " ms " );
30 System . out . println ( " Acceleration : " +
31 String . format (" %.1 fx " , (10.0 * 1400) / duree )) ;
32 }) . join () ;
33
34 executor . shutdown () ;
35 }
36
37 static String charger ( String nom ) {
38 sleep (500) ;
39 System . out . println (" Charge : " + nom );
40 return nom + "[ loaded ] ";
41 }
42 static String filtrer ( String img ) {
43 sleep (300) ;
44 System . out . println (" Filtre : " + img );
45 return img + "[ filtered ]";
Concurrency & Multithreading en Java Dr. BADR EL KHALYLY
CHAPITRE 12. EXERCICES ET CHALLENGES CORRIGÉS 75
46 }
47 static String redimensionner ( String img ) {
48 sleep (200) ;
49 System . out . println (" Redimensionne : " + img );
50 return img + "[ resized ]";
51 }
52 static String sauvegarder ( String img ) {
53 sleep (400) ;
54 System . out . println (" Sauvegarde : " + img );
55 return img + "[ saved ]" ;
56 }
57 static void sleep ( long ms ) {
58 try { Thread . sleep ( ms ) ; } catch ( InterruptedException e) {}
59 }
60 }
12.4 Challenge 4 : Deadlock et résolution
Challenge: Créer et résoudre un Deadlock
Écrivez un code qui crée un deadlock, puis corrigez-le avec tryLock().
Listing 12.4 Solution Challenge 4 : Deadlock et résolution
1 import java . util . concurrent . locks .*;
2 import java . util . concurrent . TimeUnit ;
3
4 public class Challenge4 {
5 // === VERSION AVEC DEADLOCK ===
6 static final Object lockA = new Object () ;
7 static final Object lockB = new Object () ;
8
9 static void deadlockDemo () {
10 Thread t1 = new Thread (() -> {
11 synchronized ( lockA ) {
12 System . out . println (" T1 a lockA ") ;
13 try { Thread . sleep (100) ; } catch ( Exception e) {}
14 synchronized ( lockB ) { // BLOQUE : T2 a lockB
15 System . out . println (" T1 a lockA et lockB ") ;
16 }
17 }
18 }) ;
19 Thread t2 = new Thread (() -> {
20 synchronized ( lockB ) {
21 System . out . println (" T2 a lockB ") ;
22 try { Thread . sleep (100) ; } catch ( Exception e) {}
23 synchronized ( lockA ) { // BLOQUE : T1 a lockA
24 System . out . println (" T2 a lockB et lockA ") ;
25 }
Concurrency & Multithreading en Java Dr. BADR EL KHALYLY
CHAPITRE 12. EXERCICES ET CHALLENGES CORRIGÉS 76
26 }
27 }) ;
28 t1 . start () ; t2 . start () ;
29 // DEADLOCK ! Ni T1 ni T2 ne peut continuer
30 }
31
32 // === VERSION CORRIGEE avec tryLock ===
33 static final ReentrantLock rlockA = new ReentrantLock () ;
34 static final ReentrantLock rlockB = new ReentrantLock () ;
35
36 static void sansDeadlock () {
37 Thread t1 = new Thread (() -> {
38 while ( true ) {
39 try {
40 if ( rlockA . tryLock (1 , TimeUnit . SECONDS )) {
41 try {
42 if ( rlockB . tryLock (1 ,
TimeUnit . SECONDS )) {
43 try {
44 System . out . println (" T1 : les
deux locks acquis !" );
45 return ;
46 } finally { rlockB . unlock () ; }
47 }
48 } finally { rlockA . unlock () ; }
49 }
50 } catch ( InterruptedException e) { return ; }
51 System . out . println (" T1 : retry ... ") ;
52 }
53 }) ;
54
55 Thread t2 = new Thread (() -> {
56 while ( true ) {
57 try {
58 if ( rlockB . tryLock (1 , TimeUnit . SECONDS )) {
59 try {
60 if ( rlockA . tryLock (1 ,
TimeUnit . SECONDS )) {
61 try {
62 System . out . println (" T2 : les
deux locks acquis !" );
63 return ;
64 } finally { rlockA . unlock () ; }
65 }
66 } finally { rlockB . unlock () ; }
67 }
68 } catch ( InterruptedException e) { return ; }
69 System . out . println (" T2 : retry ... ") ;
70 }
71 }) ;
72 t1 . start () ; t2 . start () ;
Concurrency & Multithreading en Java Dr. BADR EL KHALYLY
CHAPITRE 12. EXERCICES ET CHALLENGES CORRIGÉS 77
73 }
74
75 public static void main ( String [] args ) {
76 // deadlockDemo () ; // Decommenter pour voir le deadlock
77 sansDeadlock () ; // Version corrigee
78 }
79 }
12.5 Challenge 5 : Usine de Chaussures Améliorée
Challenge: Usine avec pannes et réparations
Améliorez l'usine de chaussures du Chapitre 10 :
Chaque machine a 10% de chance de tomber en panne
En cas de panne, la production est redistribuée aux machines restantes
Utilisez exceptionally() pour gérer les pannes
Listing 12.5 Solution Challenge 5 : Usine avec gestion des pannes
1 import java . util . concurrent .*;
2 import java . util .*;
3 import java . util . stream .*;
4
5 public class Challenge5 {
6 static final int NB_MACHINES = 5;
7 static final int TOTAL = 70;
8
9 public static void main ( String [] args ) {
10 ExecutorService executor =
Executors . newFixedThreadPool ( NB_MACHINES );
11 int parMachine = TOTAL / NB_MACHINES ;
12
13 List < CompletableFuture < List < String >> > futures =
14 IntStream . rangeClosed (1 , NB_MACHINES )
15 . mapToObj ( id -> CompletableFuture
16 . supplyAsync (() -> produireAvecPanne (id ,
parMachine ) , executor )
17 . exceptionally ( ex -> {
18 System . err . println (" [ PANNE Machine " + id + "]
" +
19 ex . getMessage () );
20 // Machine de secours prend le relais
21 System . out . println (" [ SECOURS ]
Redistribution ... ");
22 return produireSecours (id , parMachine );
23 })
24 . thenApply ( chaussures -> {
25 System . out . println (" [ QC ] Controle " +
26 chaussures . size () + " paires ") ;
Concurrency & Multithreading en Java Dr. BADR EL KHALYLY
CHAPITRE 12. EXERCICES ET CHALLENGES CORRIGÉS 78
27 return chaussures ;
28 })
29 ). collect ( Collectors . toList () );
30
31 CompletableFuture . allOf ( futures . toArray ( new
CompletableFuture [0]) )
32 . thenRun (() -> {
33 int total = futures . stream ()
34 . mapToInt ( f -> f. join () . size () ). sum () ;
35 System . out . println ("\ n === TOTAL PRODUIT : " + total
+ " === ");
36 }) . join () ;
37
38 executor . shutdown () ;
39 }
40
41 static List < String > produireAvecPanne ( int machineId , int
quantite ) {
42 List < String > resultat = new ArrayList < >() ;
43 for ( int i = 1; i <= quantite ; i ++) {
44 if ( Math . random () < 0.10) { // 10% de panne
45 throw new RuntimeException (
46 " Machine " + machineId + " en panne a l ' item "
+ i );
47 }
48 try { Thread . sleep (50) ; } catch ( InterruptedException
e) { break ; }
49 resultat . add ( "M" + machineId + " -P " + i );
50 }
51 return resultat ;
52 }
53
54 static List < String > produireSecours ( int machineId , int
quantite ) {
55 List < String > resultat = new ArrayList < >() ;
56 for ( int i = 1; i <= quantite ; i ++) {
57 try { Thread . sleep (80) ; } catch ( InterruptedException
e) { break ; }
58 resultat . add ( " SECOURS - M" + machineId + " -P" + i) ;
59 }
60 System . out . println (" [ SECOURS ] Machine " + machineId +
61 " remplacement termine : " + quantite + " paires ");
62 return resultat ;
63 }
64 }
Fin du cours
Concurrency & Multithreading en Java Dr. BADR EL KHALYLY
CHAPITRE 12. EXERCICES ET CHALLENGES CORRIGÉS 79
Dr. BADR EL KHALYLY
Concurrency & Multithreading en Java Guide Complet
Concurrency & Multithreading en Java Dr. BADR EL KHALYLY