0% ont trouvé ce document utile (0 vote)
4 vues80 pages

Multitreahding en Java

Ce document est un guide complet sur la concurrence et le multithreading en Java, couvrant des concepts fondamentaux aux techniques avancées. Il aborde des sujets tels que la création et la gestion des threads, la programmation asynchrone, la synchronisation, et l'utilisation de l'Executor Framework. Le contenu est structuré en chapitres détaillés, incluant des exercices pour renforcer l'apprentissage.

Transféré par

Galand Noah
Copyright
© All Rights Reserved
Nous prenons très au sérieux les droits relatifs au contenu. Si vous pensez qu’il s’agit de votre contenu, signalez une atteinte au droit d’auteur ici.
Formats disponibles
Téléchargez aux formats PDF, TXT ou lisez en ligne sur Scribd
0% ont trouvé ce document utile (0 vote)
4 vues80 pages

Multitreahding en Java

Ce document est un guide complet sur la concurrence et le multithreading en Java, couvrant des concepts fondamentaux aux techniques avancées. Il aborde des sujets tels que la création et la gestion des threads, la programmation asynchrone, la synchronisation, et l'utilisation de l'Executor Framework. Le contenu est structuré en chapitres détaillés, incluant des exercices pour renforcer l'apprentissage.

Transféré par

Galand Noah
Copyright
© All Rights Reserved
Nous prenons très au sérieux les droits relatifs au contenu. Si vous pensez qu’il s’agit de votre contenu, signalez une atteinte au droit d’auteur ici.
Formats disponibles
Téléchargez aux formats PDF, TXT ou lisez en ligne sur Scribd

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

Vous aimerez peut-être aussi