0% ont trouvé ce document utile (0 vote)
10 vues186 pages

Concepts de Threads en Java

Ce document présente les concepts de base des threads en Java. Il définit notamment ce qu'est le multitâche, les threads et les processus, et décrit comment créer et lancer des threads en Java à l'aide de la classe Thread.

Transféré par

Ranto Ralambosoa
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)
10 vues186 pages

Concepts de Threads en Java

Ce document présente les concepts de base des threads en Java. Il définit notamment ce qu'est le multitâche, les threads et les processus, et décrit comment créer et lancer des threads en Java à l'aide de la classe Thread.

Transféré par

Ranto Ralambosoa
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

Threads

Université de Nice - Sophia Antipolis


Version 3.6.1 – 15/4/12
Richard Grin
Plan de cette partie
‰ Présentation des threads
‰ Classe Thread
‰ Synchronisation entre threads
‰ wait et notify
‰ Difficultés du multitâche
‰ Divers compléments
‰ Nouvelle API pour la concurrence
‰ Fork-join

R. Grin Java : threads page 2


‰ Ce support expose les concepts de base sur
les threads en Java
‰ Quelques points de la nouvelle API pour la
concurrence sont étudiés à la fin du support

R. Grin Java : threads page 3


Définitions
‰ Un programme est multitâche quand il lance
(ou peut lancer) l’exécution de plusieurs
parties de son code en même temps
‰ A un moment donné, il comporte plusieurs
points d’exécution liés aux différentes parties
qui s'exécutent en parallèle

R. Grin Java : threads page 4


Systèmes d’exploitation
‰ Tous les systèmes d’exploitation modernes sont
multitâches et ils permettent l’exécution de
programmes multitâches
‰ Sur une machine monoprocesseur cette
exécution en parallèle est simulée
‰ Si le système est préemptif, il peut à tout
moment prendre la main à un programme pour
la donner à un autre
‰ Sinon, un programme garde la main jusqu’à ce
qu’il la cède à un autre

R. Grin Java : threads page 5


Threads et processus
‰ Le multitâche s’appuie sur les processus ou les
threads (fil d’exécution en français)
‰ Chaque processus a son propre espace
mémoire (espace où sont rangées les valeurs
des variables utilisées par le processus)
‰ Un processus peut lancer plusieurs threads qui
se partagent le même espace mémoire ; ils
peuvent accéder aux mêmes variables
‰ Un thread prend moins de ressources système
qu’un processus
R. Grin Java : threads page 6
Exemples de thread
‰ L’interface graphique avec l’utilisateur lance un
thread pour charger une image pour continuer à
traiter les événements générés par les actions de
l’utilisateur
‰ Le serveur réseau qui attend les demandes de
connexions venant des autres machines lance un
thread pour traiter chacune des demandes
‰ La multiplication de 2 matrices (m, p) et (p, n)
peut être effectuée en parallèle par m × n threads

R. Grin Java : threads page 7


Utilité du multitâche
‰ Sur une machine multiprocesseurs il permet
d’améliorer les performances en répartissant les
différentes tâches sur différents processeurs
‰ Par exemple, le calcul du produit de 2 matrices peut
être réparti en n tâches parallèles (ou k tâches si le
nombre k de processeurs est inférieur à n)
‰ La répartition des tâches sur les processeurs est le
plus souvent faite automatiquement par le système
qui offre le multitâche

R. Grin Java : threads page 8


Utilité du multitâche (2)
‰ Sur une machine monoprocesseur, il peut aussi
être intéressant d’utiliser le multitâche pour
n modéliser plus simplement (simulation par exemple)
n profiter des temps de pose d’une tâche (attente
d’entrées-sorties ou d’une action de l’utilisateur)
pour exécuter d’autres tâches
n réagir plus vite aux actions de l’utilisateur en rejetant
une tâche longue et non-interactive dans un autre
thread (par exemple, chargement d’une image ou
lecture de données qui proviennent d’un réseau)

R. Grin Java : threads page 9


Problèmes du multitâche
‰ Il est souvent plus difficile d’écrire un
programme multitâche
‰ Et surtout, il est difficile de déboguer un
programme qui utilise le multitâche

R. Grin Java : threads page 10


Java et le multitâche

‰ Java supporte l’utilisation des threads


‰ A l’inverse de la plupart des autres langages, le
programmeur n'a pas à utiliser des librairies
natives du système pour écrire des programmes
multitâches

R. Grin Java : threads page 11


Threads en Java

‰ A tout thread Java sont associés


n un objet qui détermine le code qui est
exécuté par le thread
n un objet qui « contrôle » le thread et le
représente auprès des objets de
l’application ; on l’appellera le « contrôleur
de thread »

R. Grin Java : threads page 12


Interface Runnable

‰ La classe de l’objet qui détermine le


code à exécuter doit implémenter
l’interface Runnable

public interface Runnable {


void run();
}
méthode qui détermine
le code à exécuter par
le thread
R. Grin Java : threads page 13
Un thread n’est pas un objet !
‰ La méthode run() « saute » d’un objet à
l’autre en exécutant les méthodes des
classes de ces objets :
o1.m1();
o2.m2();
...
‰ Un thread est une unité d’exécution qui, à
un moment donné, exécute une méthode
‰ A un autre moment, ce même thread pourra
exécuter une autre méthode d’une autre
classe

R. Grin Java : threads page 14


Contrôleur de thread
‰ Le contrôleur d’un thread est une instance de la
classe Thread (ou d’une classe fille) qui
n est l’intercesseur entre le thread et les objets
de l’application
n permet de contrôler l’exécution du thread
(pour le lancer en particulier)
n a des informations sur l’état du thread (son
nom, sa priorité, s’il est en vie ou non,…)

R. Grin Java : threads page 15


Classe Thread

R. Grin Java : threads page 16


Classe Thread
‰ Elle implémente l’interface Runnable (mais la
méthode run() ne fait rien)
‰ Une instance d’une classe fille de Thread peut
donc être à la fois un contrôleur de thread et
définir le code à exécuter
‰ Lorsqu’une instance de Thread est créée, il faut
indiquer le code du thread qui sera contrôlé par
cette instance

R. Grin Java : threads page 17


2 façons de créer
un contrôleur de thread
‰ 1ère façon : utiliser le constructeur
Thread(Runnable) de la classe Thread :
1. créer un Runnable (le code qui sera

exécuté par le thread)


2. le passer au constructeur de Thread

‰ 2ème façon : créer une instance d’une classe


fille de la classe Thread qui redéfinit la
méthode run()

R. Grin Java : threads page 18


Créer un contrôleur de thread
avec l’interface Runnable
class Tache implements Runnable {
. . .
public void run() {
// Code qui sera exécuté par le thread
. . .
}
}

Tache tache = new Tache(…);


Thread t = new Thread(tache) ;
R. Grin Java : threads page 19
Créer un contrôleur de thread avec
une classe fille de la classe Thread
class ThreadTache extends Thread {
. . .
public void run() {
// Code qui sera exécuté par le thread
. . .
}
}

ThreadTache threadTache = new ThreadTache(…);

R. Grin Java : threads page 20


Quelle façon utiliser ?
‰ Si on veut hériter d’une autre classe pour la
classe qui contient la méthode run(), on est
obligé de choisir la 1ère façon
(Thread(Runnable))
‰ Il est aussi plus simple d’utiliser la 1ère façon
pour partager des données entre plusieurs
threads
‰ Sinon, l’écriture du code est (légèrement)
plus simple en utilisant la 2ème façon

R. Grin Java : threads page 21


Nom d’un thread
‰ Des constructeurs de Thread permettent de
donner un nom au thread en le créant
‰ Le nom va faciliter le repérage des threads
durant la mise au point
‰ Pour récupérer le nom du thread « courant » :
[Link]().getName()

R. Grin Java : threads page 22


Lancer l’exécution d’un thread
‰ On appelle la méthode start() du
contrôleur de thread :
[Link]();
‰ Le code du Runnable s’exécute en parallèle
au code qui a lancé le thread
‰ Attention, une erreur serait d’appeler
directement la méthode run() : la méthode
run() serait exécutée par le thread qui l’a
appelée et pas par un nouveau thread
R. Grin Java : threads page 23
Relancer l’exécution d’un thread

‰ On ne peut relancer un thread qui a déjà été


lancé
‰ Si l’exécution de la méthode run du thread
n’est pas encore terminée, on obtient une
[Link]
‰ Si elle est terminée, aucune exception n’est
lancée mais rien n’est exécuté

R. Grin Java : threads page 24


Vie du contrôleur de thread
‰ Le contrôleur de thread existe indépendamment
du thread,
n avant le démarrage du thread,
par exemple, pour initialiser des variables
d'instances du contrôleur
n après la fin de l’exécution de ce thread,
par exemple, pour récupérer des valeurs
calculées pendant l’exécution du thread et
rangées dans des variables d’instances du
contrôleur

R. Grin Java : threads page 25


Utilisation d’une classe interne

‰ La méthode run est public


‰ Si on ne souhaite pas qu’elle soit appelée
directement, on peut utiliser une classe
interne à une classe fille de Thread pour
implémenter Runnable

R. Grin Java : threads page 26


Utilisation d’une
classe interne anonyme
‰ Si le code d’une tâche comporte peu de
lignes (sinon ça devient vite illisible), on peut
lancer son exécution en parallèle en utilisant
une classe anonyme :
Thread t = new Thread() {
. . . Ou encore :
public void run() { new Thread(
. . . new Runnable() {
} . . .
public void run() {
}; . . .
[Link](); }
});
R. Grin Java : threads page 27
Méthodes publiques principales
de la classe Thread
void start()
static void sleep(long)
throws InterruptedException
void join() throws InterruptedException
void interrupt()
static boolean interrupted()
int getPriority()
void setPriority(int)
static Thread currentThread()
static void yield()

R. Grin Java : threads page 28


Thread courant
‰ La méthode currentThread montre bien
qu’un thread n’est pas un objet
‰ Placée dans une méthode de n’importe
quelle classe, elle retourne l’objet Thread qui
contrôle le thread qui exécute cette méthode
au moment où currentThread est appelé
‰ On peut ainsi faire un traitement spécial dans
le cas où la méthode est exécuté par un
certain thread (par exemple le thread de répartition
des événements dans un GUI)

R. Grin Java : threads page 29


Attente de la fin d’un thread
‰ Soit un thread t
[Link]();
attend la fin de l’exécution du thread contrôlé
par t
‰ On remarquera qu’après la fin de l’exécution
du thread t on peut encore envoyer de
messages à l’objet contrôleur de thread t
‰ On peut aussi interroger la tâche exécutée
par le thread pour récupérer le résultat d’un
calcul effectué par le thread
R. Grin Java : threads page 30
Passer la main
‰ La méthode static de la classe Thread
public static void yield()
permet de passer la main à un autre thread
de priorité égale ou supérieure
‰ Elle permet d'écrire des programmes plus
portables qui s'adaptent mieux aux systèmes
multitâches non préemptifs

R. Grin Java : threads page 31


Dormir

‰ La méthode static de la classe Thread


public static void sleep(long millis)
throws InterruptedException
fait dormir le thread qui l’appelle
‰ Si elle est exécutée dans du code
synchronisé, le thread ne perd pas le moniteur
(au contraire de wait())

R. Grin Java : threads page 32


Méthode interrupt()
‰ [Link]()
demande au thread contrôlé par t
d’interrompre son exécution
‰ Cette méthode n’interrompt pas brutalement le
thread mais positionne son état « interrupted »
(l’indicateur d’interruption)

R. Grin Java : threads page 33


Méthode interrupted()
‰ La méthode static interrupted()
renvoie la valeur de l’état « interrupted » du
thread courant
‰ Attention, après l’exécution de cette méthode,
l’état « interrupted » du thread est mis à false

R. Grin Java : threads page 34


Interrompre un thread
‰ Pour qu’un thread interrompe vraiment son
exécution, il doit participer activement à sa
propre interruption
‰ 2 cas :
n le thread est en attente, par les méthodes
sleep, wait, join, ou en attente d’un
entrée-sortie
n toutes les autres situations…

R. Grin Java : threads page 35


Interrompre un thread en attente (1)
‰ Si le thread est en attente avec la méthode
sleep, ou par wait ou join, la méthode
interrupt provoque la levée d’une
[Link]
‰ Le thread « interrompu » gère cette
interruption dans un bloc catch qui traite
cette exception
‰ Malheureusement, une exception n’est pas levé si le
thread est en attente d’une entrée-sortie (sauf avec
InterruptibleChannel de NIO) ; seul l’indicateur
d’interruption est positionné
R. Grin Java : threads page 36
Interrompre un thread en attente (2)
‰ Souvent le thread va repositionner lui-même
l’état « interrupted » (pour être repéré plus tard
par un [Link]()) car la
levée d’une InterruptedException enlève
l’état « interrupted » du thread :
try {
. . .
catch(InterruptedException e) {
[Link]().interrupt();
}
‰ Voir l’API [Link] pour les interruptions des
attentes des entrées-sorties
R. Grin Java : threads page 37
Interrompre un thread
qui n’est pas en attente (1)
‰ En ce cas le thread doit vérifier
périodiquement s’il a été interrompu par la
méthode static [Link]()
‰ Par exemple avec une boucle du type
while (! [Link]()) {
. . . // faire son travail
}

R. Grin Java : threads page 38


Interrompre un thread
qui n’est pas en attente (2)
‰ Attention, l’appel de la méthode
interrupted() annule l’interruption : si
cette méthode renvoie true pour signaler
une interruption et que le thread décide de
l’ignorer, un nouvel appel à cette méthode
renverra false (sauf si le thread est à
nouveau interrompu)

R. Grin Java : threads page 39


Réaction à une interruption
‰ Le thread interrompu peut réagir
différemment à une interruption
‰ Il peut interrompre son exécution avec un
return ou en lançant une exception
‰ Il peut effectuer un traitement particulier en
réaction à l’interruption mais ne pas
s’interrompre
‰ Il peut ignorer l’interruption

R. Grin Java : threads page 40


Exemple 1
for (int i = 0; i < 100; i++) {
try {
[Link](1000);
}
catch (InterruptedException e) {
// S’interrompt après avoir fait
// le ménage
...
return;
}
// Effectue un traitement
traitement();
}
R. Grin Java : threads page 41
Exemple 2
for (int i = 0; i < 100; i++) {
// Effectue un traitement lourd
traitement();
if ([Link]()) {
// S’interrompt après avoir fait
// le ménage
...
return;
}

R. Grin Java : threads page 42


Threads et exceptions
‰ Si une exception n’est pas traitée (par un bloc
try-catch), elle interrompt l’exécution du
thread courant mais pas des autres threads
‰ La méthode run ne peut déclarer lancer une
exception contrôlée car elle redéfinit une
méthode sans clause « throws »
‰ Une exception non saisie peut être saisie par
le groupe du thread (étudié plus loin)

R. Grin Java : threads page 43


Synchronisation entre threads

R. Grin Java : threads page 44


Section critique
‰ L’utilisation de threads peut entraîner des
besoins de synchronisation pour éviter les
problèmes liés aux accès simultanés aux
variables
‰ En programmation, on appelle section critique
une partie du code qui ne peut être exécutée en
même temps par plusieurs threads sans risquer
de provoquer des anomalies de fonctionnement

R. Grin Java : threads page 45


Exemple de problème
‰ Si x = 2, le code x = x + 1;
exécuté par 2 threads, peut donner en fin
d’exécution 3 ou 4 suivant l’ordre d’exécution :
1. T1 : lit la valeur de x (2)
2. T2 : lit la valeur de x (2)
3. T1 : calcul de x + 1 (3)
4. T2 : calcul de x + 1 (3)
5. T1 : range la valeur calculée dans x (3)
6. T2 : range la valeur calculée dans x (3)
‰ x contient 3 au lieu de 4 !
R. Grin Java : threads page 46
Nécessaire synchronisation
‰ Il faut donc éviter l’exécution simultanée de
sections de code critiques par plusieurs
threads
‰ En Java le code synchronisé sur un objet est
utilisé pour synchroniser les threads et les
empêcher d’exécuter en même temps des
portions de code
‰ Plusieurs threads ne peuvent exécuter en
même temps du code synchronisé sur un
même objet
R. Grin Java : threads page 47
2 possibilités pour synchroniser
du code sur un objet o
‰ Méthode synchronisée m (avec un message
envoyé à l’objet o : o.m(…)) :
public synchronized int m(…) { . . }
‰ Bloc synchronisé sur l’objet o :
synchronized(o) {
// le code synchronisé
. . .
}

R. Grin Java : threads page 48


Moniteur d’un objet
‰ La protection du code synchronisé contre les
accès multiples repose sur les moniteurs des
objets
‰ Chaque objet Java possède un moniteur
‰ À un moment donné, un seul thread peut
posséder le moniteur d’un objet
‰ Si d’autres threads veulent acquérir le même
moniteur, ils sont mis en attente, en attendant
que le premier thread rende le moniteur

R. Grin Java : threads page 49


Acquisition et restitution
d’un moniteur
‰ Un thread t acquiert automatiquement le
moniteur d’un objet o en exécutant du code
synchronisé sur cet objet o
‰ t rend le moniteur en quittant le code
synchronisé (ou se met en attente en appelant
[Link]() dans le code synchronisé)
‰ Il peut quitter le code synchronisé normalement,
ou si une exception est lancée et non saisie
dans le code synchronisé

R. Grin Java : threads page 50


Résumé
‰ Tant que t exécute du code synchronisé sur un
objet o, les autres threads ne peuvent exécuter
du code synchronisé sur ce même objet o (le
même code, ou n’importe quel autre code
synchronisé sur o) ; ils sont mis en attente
‰ Lorsque t quitte le code synchronisé ou se met
en attente par [Link](), un des threads en
attente peut commencer à exécuter le code
synchronisé
‰ Les autres threads en attente auront la main à
tour de rôle (si tout se passe bien…)
R. Grin Java : threads page 51
Exemple
public class Compte {
private double solde;

public void deposer(double somme) {


solde = solde + somme;
}

public double getSolde() {


return solde;
}
}

R. Grin Java : threads page 52


Exemple (suite)
‰ On lance 3 threads du type suivant :
Thread t1 = new Thread() {
public void run() {
for (int i = 0; i < 100; i++) {
[Link](1000);
}
}
};
‰ A la fin de l’exécution, on n’obtient pas
nécessairement 300.000

R. Grin Java : threads page 53


Exemple (fin)
‰ Pour éviter ce problème il faut rendre deposer
synchronisée :
public synchronized
void deposer(double somme)
‰ En fait, pour sécuriser la classe contre les accès
multiples, il faudra aussi rendre getSolde
synchronized car, en Java, un double n’est pas
lu en une opération atomique. Il faut donc éviter
que getSolde ne soit exécuté en même temps
que deposer

R. Grin Java : threads page 54


Favoriser la détection des problèmes
‰ En fait, si on exécute le code précédent sans
rendre deposer synchronized, on obtiendra
bien souvent (mais pas toujours…) le bon
résultat
‰ Ça dépend du fonctionnement du multitâche
du système d’exploitation sous-jacent, et de
la JVM
‰ Pour repérer plus facilement les problèmes
de multitâche, on peut ajouter des appels aux
méthodes yield ou sleep qui forcent le
thread à rendre la main, et permettre ainsi à
un autre thread de pouvoir s’exécuter
R. Grin Java : threads page 55
Provoquer le problème
public class Compte {
private double solde;

public void deposer(double somme) {


double soldeTemp = solde; Avec tous les
[Link](); SE et JVM, la
solde = soldeTemp + somme; main pourra
} être rendue
pendant
l’exécution de
public double getSolde() {
cette méthode
return solde;
}
}
R. Grin Java : threads page 56
Synchronisation et performances
‰ L’exécution de code synchronisé peut nuire
aux performances
‰ Il peut provoquer des blocages entre threads ;
peu de perte de performance s’il n’y a pas de
blocage
‰ Du code synchronisé peut empêcher des
optimisations (inlining) au moment de la
compilation

R. Grin Java : threads page 57


Limiter la portée du code synchronisé
‰ Il faut synchroniser le moins de code possible
pour faciliter les accès multiples (les
performances seront meilleures s’il y a moins
de threads en attente d’un moniteur)
‰ Attention, éviter d’appeler à l’intérieur d’une
portion synchronisée des méthodes qu’un
client peut redéfinir (dans une classe fille) ;
en effet, une redéfinition non appropriée peut
provoquer des pertes de performance ou
même des inter-blocages
R. Grin Java : threads page 58
Méthodes de classe synchronisées
‰ Si on synchronise une méthode static, on
bloque le moniteur de la classe
‰ On bloque ainsi tous les appels à des
méthodes synchronisées de la classe (mais
pas les appels synchronisés sur une instance
de la classe)

R. Grin Java : threads page 59


Méthode synchronisée et héritage

‰ La redéfinition d’une méthode synchronisée


dans une classe fille peut ne pas être
synchronisée
‰ De même, la redéfinition d’une méthode non
synchronisée peut être synchronisée

R. Grin Java : threads page 60


Synchronisation et
visibilité de modifications
‰ La synchronisation permet d’assurer la
visibilité par les autres threads de la
modification d’une variable par un thread (voir
« happens-before » dans la section
« difficultés du multi-tâche » dans la suite de
ce cours)

R. Grin Java : threads page 61


wait et notify

R. Grin Java : threads page 62


Exécution conditionnelle
‰ Lorsqu’un programme est multi-tâche, la
situation suivante peut se rencontrer :
n Un thread t1 ne peut continuer son
exécution que si une condition est remplie
n Le fait que la condition soit remplie ou non
dépend d’un autre thread t2
‰ Par exemple, t1 a besoin du résultat d’un
calcul effectué par t2

R. Grin Java : threads page 63


Exécution conditionnelle (2)
‰ Une solution coûteuse serait que t1 teste la
condition à intervalles réguliers
‰ Les méthodes wait() et notify() de la
classe Object permettent de programmer
plus efficacement ce genre de situation

R. Grin Java : threads page 64


Schéma d’utilisation de wait-notify
‰ Cette utilisation demande un travail coopératif
entre les threads t1 et t2 :
1. Ils se mettent d’accord sur un objet commun
objet
2. Arrivé à l’endroit où il ne peut continuer que si
la condition est remplie, t1 se met en attente :
[Link]();
3. Quand t2 a effectué le travail pour que la
condition soit remplie, il le notifie :
[Link]();
ce qui débloque t1
R. Grin Java : threads page 65
Besoin de synchronisation
‰ Le mécanisme d’attente-notification lié à un
objet met en jeu l’état interne de l’objet ; pour
éviter des accès concurrent à cet état interne,
une synchronisation est nécessaire
‰ Les appels [Link]() et
[Link]() (et [Link]())
ne peuvent donc être effectués que dans du
code synchronisé sur objet
‰ Autrement dit, wait et notify ne peuvent
être lancés que dans une section critique
R. Grin Java : threads page 66
Méthode wait()
‰ public final void wait()
throws InterruptedException
‰ [Link]()
n nécessite que le thread en cours possède le
moniteur de objet
n bloque le thread qui l’appelle, jusqu’à ce qu’un
autre thread appelle la méthode
[Link]() ou [Link]()
n libère le moniteur de l’objet (l’opération
« blocage du thread – libération du moniteur »
est atomique)
R. Grin Java : threads page 67
Utilisation de wait
‰ Mauvaise utilisation :
if (!condition) [Link]();
n si on quitte l’attente avec le wait(), cela
signifie qu’un autre thread a notifié que la
condition était remplie
n mais, après la notification, et avant le
redémarrage de ce thread, un autre thread a pu
prendre la main et modifier la condition
‰ Le bon code (dans du code synchronisé sur le
même objet que le code qui modifie la condition) :
while (!condition) {
[Link]();
}
R. Grin Java : threads page 68
Méthode notifyAll()

‰ public final void notifyAll()


‰ [Link]()
n nécessite que le thread en cours possède le
moniteur de objet
n débloque tous les threads qui s’étaient bloqués
sur l’objet avec [Link]()

R. Grin Java : threads page 69


Méthode notifyAll()
‰ Un seul des threads débloqués va récupérer le
moniteur ; on ne peut prévoir lequel
‰ Les autres devront attendre qu’il relâche le
moniteur pour être débloqués à tour de rôle, mais
ils ne sont plus bloqués par un wait
‰ En fait, souvent ils se bloqueront à nouveau eux-
mêmes (s’ils sont dans une boucle while avec
wait)

R. Grin Java : threads page 70


Méthode notify()
‰ [Link]()
n idem notifyAll() mais

nne débloque qu’un seul thread


‰ On ne peut prévoir quel sera le thread débloqué
et, le plus souvent, il vaut donc mieux utiliser
notifyAll()

R. Grin Java : threads page 71


Déblocage des threads

‰ Le thread débloqué (et élu) ne pourra reprendre


son exécution que lorsque le thread qui l’a notifié
rendra le moniteur de l’objet en quittant sa portion
de code synchronisé
‰ Le redémarrage et l’acquisition se fait dans une
opération atomique

R. Grin Java : threads page 72


notify perdus

‰ Si un notifyAll() (ou notify()) est exécuté


alors qu’aucun thread n’est en attente, il est
perdu : il ne débloquera pas les wait()
exécutés ensuite

R. Grin Java : threads page 73


Exemple avec wait-notify
‰ Les instances d’une classe Depot contiennent
des jetons
‰ Ces jetons sont
n déposés par un producteur

n consommés par des consommateurs

‰ Les producteurs et consommateurs sont des


threads

R. Grin Java : threads page 74


Exemple avec wait-notify
public class Depot {
private int nbJetons = 0;
public synchronized void donneJeton() {
try {
while (nbJetons == 0) {
wait();
}
nbJetons--;
}
catch (InterruptedException e) {}
}

R. Grin Java : threads page 75


Exemple avec wait-notify
public synchronized void recois(int n) {
nbJetons += n;
notifyAll();
}

R. Grin Java : threads page 76


Variante de wait
‰ Si on ne veut pas attendre éternellement une
notification, on peut utiliser une des variantes
suivantes de wait :
public void wait(long timeout)
public void wait(long timeout,
int nanos)
‰ Dans ce cas, le thread doit gérer lui-même le
fait de connaître la cause de son déblocage
(notify ou temps écoulé)

R. Grin Java : threads page 77


Moniteurs réentrants

‰ Un thread qui a acquis le moniteur d’un objet


peut exécuter les autres méthodes
synchronisées de cet objet ; il n’est pas
bloqué en demandant à nouveau le moniteur

R. Grin Java : threads page 78


Affectations atomiques
‰ Il est inutile de synchroniser une partie de code
qui ne fait qu’affecter (ou lire) une valeur à une
variable de type primitif de longueur 32 bits ou
moins (int, short, …)
‰ En effet, la spécification du langage Java
spécifie qu’une telle affectation ne peut être
interrompue pour donner la main à un autre
thread
‰ Mais, cette spécification n’assure rien pour les
affectations de double et de long !

R. Grin Java : threads page 79


Stopper un thread
‰ Si on stoppe un thread il arrête de s’exécuter
‰ Il ne peut reprendre son exécution ; si on veut
pouvoir suspendre et reprendre l’exécution du
thread, voir suspend et resume
‰ stop() est deprecated car le thread stoppé
peut laisser des objets dans un état
inconsistant car il relâche leur moniteur quand
il arrête son exécution
‰ On peut simuler stop en utilisant une variable

R. Grin Java : threads page 80


Simuler stop()
‰ Pour rendre possible l’arrêt d'un thread T, on peut
utiliser une variable arretThread visible depuis
T et les threads qui peuvent stopper T :
n T initialise arretThread à false lorsqu’il

démarre
n pour stopper T, un autre thread met
arretThread à true
n T inspecte à intervalles réguliers la valeur de
arretThread et s’arrête quand
arretThread a la valeur true

R. Grin Java : threads page 81


Simuler stop()
‰ arretThread doit être déclarée volatile si
elle n’est pas accédée dans du code synchronisé
(volatile est étudié plus loin dans ce cours)

R. Grin Java : threads page 82


Interrompre un thread
‰ Le mécanisme décrit précédemment pour
stopper un thread ne fonctionne pas si le
thread est en attente (wait , sleep, attente
d’un entrée-sortie avec le paquetage [Link]
depuis le SDK 1.4)
‰ Dans ce cas, on utilise interrupt() qui
interrompt les attentes et met l’état du thread
à « interrupted » (voir la section sur la classe
Thread du début de ce cours)

R. Grin Java : threads page 83


Suspendre et relancer un thread
‰ suspend() et resume() sont deprecated car
ils favorisent les interblocages (un thread
suspendu ne relâche pas les moniteurs qu’il
possède)
‰ On peut les remplacer en utilisant une variable
suspendreThread comme pour la méthode
stop()
‰ Comme il faut pouvoir reprendre l’exécution on
doit en plus utiliser wait() et notify()

R. Grin Java : threads page 84


Simuler suspend et resume
‰ Pendant son exécution le thread scrute la
valeur de suspendreThread
‰ Si la valeur est true, le thread se met en
attente avec wait sur un objet o
‰ Quand un autre thread veut relancer
l’exécution du thread, il met la variable
suspendreThread à false et il appelle
notify sur l’objet o
‰ Rappel : les appels de wait et notify doivent se
faire dans des sections synchronisées sur o
R. Grin Java : threads page 85
Cycle de vie d'un thread
sleep()
Bloqué
new wait()
Thread()
notify()

Nouveau start() Eligible pour En


thread l'exécution exécution
yield()

exit()en
fin d'exécution
Mort

R. Grin Java : threads page 86


Threads démons
‰ 2 types de threads
n les threads utilisateur

n les démons

‰ La différence :
n la JVM fonctionne tant qu’il reste des threads
utilisateurs en exécution
n la JVM s’arrête s’il ne reste plus que des
démons
‰ Les démons sont là seulement pour rendre service
aux threads utilisateur. Exemple : ramasse-miettes
R. Grin Java : threads page 87
Threads démons
‰ La méthode void setDaemon(boolean on) de
la classe Thread permet d’indiquer que le thread
sera un démon (thread utilisateur par défaut)
‰ Elle doit être appelée avant le démarrage du
thread par l’appel de start()

R. Grin Java : threads page 88


Difficultés liées au multitâche

R. Grin Java : threads page 89


Difficultés du multitâche
‰ Si un thread t doit attendre 2 notify de 2 autres
threads t1 et t2, ce serait une faute de coder
wait();
wait();

R. Grin Java : threads page 90


Difficultés du multitâche
‰ En effet, les 2 notify() peuvent arriver
« presque en même temps » :
n le thread t1 envoie un 1
er notify() qui

débloque le 1er wait() ; il relâche ensuite le


moniteur et permet ainsi à l’autre thread t2…
n … d’envoyer le 2
ème notify() avant que le

2ème wait() ne soit lancé


n le thread t reste bloqué éternellement sur le
2ème wait() (qui ne recevra jamais de notify())

R. Grin Java : threads page 91


Comment coder cette situation ?
‰ On compte le nombre de notify() avec une
variable qui est incrémentée dans une partie
critique (synchronisée) qui contient le notify()
(pour être certain que la variable représente
vraiment le nombre de notify()) :
nbNotify++;
notifyAll();

R. Grin Java : threads page 92


Comment coder cette situation
(suite)
‰ Et on se met en attente dans une boucle (dans
une portion synchronisée) :
while (nbNotify < 2) {
wait();
}
‰ Si on reçoit 1 notify() entre les 2 wait(),
nbNotify sera égal à 2 et on sortira de la
boucle sans faire le 2ème wait()

R. Grin Java : threads page 93


Éviter la synchronisation
‰ Comme la synchronisation a un coût non
négligeable, il faut essayer de l’éviter quand
c’est possible
‰ Par exemple, si un seul thread écrit une
valeur de type int qui est lue par plusieurs
autres threads, on peut se passer de
synchronisation car les opérations de lecture-
écriture de int sont atomiques
‰ Mais attention, il y a de nombreux pièges !

R. Grin Java : threads page 94


Partage de variables
par les threads
‰ Soit v une variable partagée par plusieurs threads
‰ Si le thread T modifie la valeur de v, cette
modification peut ne pas être connue
immédiatement par les autres threads
‰ Par exemple, le compilateur a pu utiliser un
registre pour conserver la valeur de v pour T
‰ La spécification de Java n’impose la connaissance
de cette modification par les autres threads que
lors de l’acquisition ou le relâchement du moniteur
d’un objet (synchronized)
R. Grin Java : threads page 95
Volatile
‰ Pour éviter ce problème, on peut déclarer la
variable v volatile
‰ On est ainsi certain que tous les threads
partageront une zone mémoire commune
pour ranger la valeur de la variable v
‰ De plus, si une variable de type long et
double est déclarée volatile, sa lecture
et son écriture est garantie atomique

R. Grin Java : threads page 96


Attention !
‰ Dans les anciennes versions de Java, une
variable volatile init ne pouvait pas servir à
indiquer l’initialisation de variables non volatiles
‰ Par exemple, dans le code suivant :
v1 = 8;
v2 = 3;
init = true;
même si init est lue comme vraie par un autre
thread, v1 et v2 n’ont peut-être pas été
initialisées car le compilateur pouvait intervertir
les instructions s’il le juge utile pour l’optimisation
R. Grin Java : threads page 97
« Happens-before »
‰ A partir de la version 5 Java formalise les cas
où une modification effectuée par un thread
est nécessairement connue par un autre
thread (ce qui signifie que, dans tous les
autres cas, cette modification n’est peut-être
par visible par l’autre thread) avec la notion
de « happens-before » (arrive-avant) définie
dans le chapitre 17 de la spécification du
langage

R. Grin Java : threads page 98


Définition de « happens-before »
‰ Une modification effectuée par un thread T1
est assurée d’être visible par un thread T2 si
et seulement si cette modification happens-
before la lecture par le thread T2
‰ Cette notion de happens-before n’est pas
nécessairement liée au temps ; il faut des
conditions bien précises pour qu’elle ait lieu
‰ Le transparent suivant résume les cas de
« bas niveau » ; l’API [Link]
ajoute d’autres cas de plus haut niveau
R. Grin Java : threads page 99
Cas de « happens-before »
‰ Dans un seul thread, une action happens-before
toute action qui arrive après dans le code
‰ La libération d’un moniteur happens-before
l’acquisition future de ce moniteur
‰ L’écriture dans un champ volatile happens-
before la lecture future de la valeur de ce champ
‰ [Link]() happens-before toutes les actions
effectuées par le thread t1
‰ Les actions d’un thread t1 happen-before les
actions d’un thread t2 qui suivent l’instruction
[Link]()
R. Grin Java : threads page 100
Transitivité de « Happens-before »
‰ Si une action A happens-before une action B et
si B happens-before une action C, alors A
happens-before C
‰ Ce qui signifie, par exemple, que toute action
effectuée par un thread t1 happens-before toute
action effectuée par un thread t2, qui suit
l’exécution de [Link]()
‰ Ce qui signifie aussi que le transparent
précédent « Attention ! » sur l’utilisation erronée
d’une variable volatile n’est plus d’actualité
R. Grin Java : threads page 101
Priorités

R. Grin Java : threads page 102


Principe de base
‰ Si plusieurs threads de même priorité sont en
exécution, on ne peut pas prévoir quel thread
va prendre la main
‰ S’ils sont en attente d’exécution, un thread de
plus grande priorité prendra toujours la main
avant un autre thread de priorité plus basse
‰ Cependant, il peut arriver exceptionnellement qu’un
thread continue son exécution alors que des threads
de priorité supérieure sont en attente d’exécution

R. Grin Java : threads page 103


Niveaux de priorité
‰ Un nouveau thread a la même priorité que le
thread qui l’a créé
‰ En général, tous les threads ont la même
priorité (NORM_PRIORITY )
‰ Il faut faire appel à la méthode setPriority
si on veut modifier cette priorité par défaut
‰ Le paramètre de setPriority doit être
inclus entre MIN_PRIORITY et
MAX_PRIORITY

R. Grin Java : threads page 104


Autres classes liées aux threads :
ThreadGroup et
ThreadLocal

R. Grin Java : threads page 105


ThreadGroup
‰ ThreadGroup représente un ensemble de
threads, qui peut lui-même comprendre un
threadGroup ; on a ainsi une arborescence
de threadGroup
‰ On peut ainsi jouer en une seule instruction
sur la priorité des threads du groupe ou sur le
fait que les thread soient des démons ou non
‰ Cette classe n’est pas très utile et il est
conseillé de ne pas l’utiliser ; on se limite ici à
l’essentiel
R. Grin Java : threads page 106
Lancer un thread d'un groupe

‰ Pour créer un thread d’un groupe, on doit utiliser


le constructeur de la classe Thread qui prend un
groupe en paramètre ; par exemple :
ThreadGroup tg =
new MonGroupe("monGroupe");
Thread t = new MonThread(tg, "monThread");
[Link]();

R. Grin Java : threads page 107


ThreadGroup et exceptions
‰ La classe ThreadGroup contient la méthode
uncaughtException(Thread t, Throwable e)
qui est exécutée quand un des threads du groupe
est stoppé par une exception non saisie
‰ On peut redéfinir cette méthode pour faire un
traitement spécial sur les autres threads
‰ Depuis le JDK 5, il est plus simple d’utiliser la
méthode static setDefaultUncaughtException
ou d’instance setUncaughtException de la
classe Thread pour traiter d’une façon particulière
les exceptions non attrapées
R. Grin Java : threads page 108
ThreadGroup et exceptions
class MyThreadGroup extends ThreadGroup {
public MyThreadGroup(String s) {
super(s);
}

public void uncaughtException(Thread t, Throwable e) {


// On met ici le traitement qui doit être exécuté
// si un des threads du groupe reçoit une exception
// non attrapée
[Link]("uncaught exception: " + e);
}

R. Grin Java : threads page 109


ThreadLocal
‰ Cette classe de [Link] permet d’associer
un état (typiquement une variable static
private) à chaque thread sans créer
d’instances différentes
‰ Puisque la valeur n’est connue que d’un seul
thread on évite ainsi les problèmes liés aux accès
concurrents
‰ C’est aussi un moyen commode d’utiliser une
valeur tout au long du déroulement d’un thread
sans utiliser une variable connue par toutes les
lignes de code concernées
R. Grin Java : threads page 110
Classe ThreadLocal<T>
Méthodes de base
‰ public void set(T valeur)
met une valeur de type T dans l’objet de type
ThreadLocal
‰ public T get()
récupère la valeur rangé dans l’objet de type
ThreadLocal
‰ public void remove()
enlève la valeur de l’objet

R. Grin Java : threads page 111


Classe ThreadLocal<T>
Initialisation de la valeur
‰ protected T initialValue()
renvoie la valeur renvoyée par get si aucun
set n’a été appelé avant (renvoie null dans
la classe ThreadLocal)
‰ Pour donner une valeur initiale à la variable
locale, il suffit de redéfinir cette méthode
dans une sous-classe de ThreadLocal

R. Grin Java : threads page 112


Exemple
public class C {
private static final ThreadLocal
tlSession = new ThreadLocal<Session>();
. . .
public static getSession() {
Session s = [Link]();
if (s == null) {
s = getFabrique().newSession();
[Link](s); En appelant [Link](),
} tout le code parcouru par un
} thread pourra travailler avec la
même session
R. Grin Java : threads page 113
InheritableThreadLocal
‰ Classe fille de ThreadLocal
‰ La différence avec ThreadLocal est que la
valeur de ces variables (si elle a une valeur) est
passée aux threads créés par le thread courant
‰ La méthode protected
T childValue(T valeurDuPère)
peut être redéfinie dans une classe fille pour
donner au thread fils une valeur calculée à partir
de la valeur du thread père (par défaut, cette
méthode renvoie la valeur passée en
paramètre)
R. Grin Java : threads page 114
Timers

R. Grin Java : threads page 115


Classes Timer et TimerTask
‰ Ces 2 classes du paquetage [Link]
permettent de lancer l’exécution de tâches à
des intervalles donnés
‰ TimerTask a une méthode run() qui
détermine la tâche à accomplir
‰ Timer détermine quand seront exécutées les
tâches qu’on lui associe
‰ Dans les 2 classes des méthodes cancel()
permettent d’interrompre une tâche ou toutes
les tâches associées à un timer
R. Grin Java : threads page 116
Configuration du timer
‰ Un timer peut déclencher une seule
exécution, ou pour déclencher des
exécutions à des intervalles réguliers
‰ Pour l’exécution à des intervalles réguliers, le
timer peut être configuré pour qu’il se cale le
mieux par rapport au début des exécutions
(méthode scheduleAtFixedRate), ou par
rapport à la dernière exécution (méthode
schedule)

R. Grin Java : threads page 117


Exemple d’utilisation de timer
final long debut =
[Link]();
TimerTask afficheTemps =
new TimerTask() {
public void run() {
[Link](
[Link]()- debut);
}
}; Démarrer
Timer timer = new Timer(); tout de suite
[Link](afficheTemps, 0, 2000);

Intervalles de 2 s
R. Grin Java : threads page 118
entre 2 affichages
Timers et swing
‰ Pour utiliser un timer qui modifie l'affichage
en Swing, il faut utiliser la classe
[Link]
‰ Cette classe utilise le thread de distribution
des événements pour faire exécuter les
tâches

R. Grin Java : threads page 119


Utiliser des classes non sûres
vis-à-vis des threads
(pas « thread-safe »)

R. Grin Java : threads page 120


Utiliser des classes non sûres
‰ Si c’est possible, synchroniser explicitement
les accès aux objets partagés en construisant
des classes qui enveloppent les classes non
sûres, avec des méthodes synchronisées
(illustré par les collections)
‰ Sinon, synchroniser les accès au niveau des
clients de ces classes ; c’est plus difficile et
moins pratique
‰ On peut aussi s’arranger pour que les
méthodes non sûres ne soient appelées que
par un seul thread (illustré par swing et le thread
de distribution des événements)
R. Grin Java : threads page 121
Exemple des collections
‰ Les nouvelles collections (Java 2) ne sont
pas sûres vis-à-vis des threads
‰ Ça permet
n d’améliorer les performances en
environnement mono-thread
n davantage de souplesse en environnement
multi-threads : par exemple, pour ajouter
plusieurs objets, on peut n’acquérir qu’une
seule fois un moniteur

R. Grin Java : threads page 122


Collections synchronisées
‰ L’API des collections permet d’obtenir une
collection synchronisée à partir d’une
collection non synchronisée, par exemple
avec la méthode static
[Link]

R. Grin Java : threads page 123


Protection des collections non
synchronisées
‰ Il faut synchroniser explicitement les
modifications des collections :
private ArrayList al;
. . .
Synchronized(al) {
[Link](…); Avantage sur
Vector : une
[Link](…);
seule acquisition
} de moniteur pour
plusieurs
modifications
R. Grin Java : threads page 124
Protection des collections non
synchronisées
‰ Cette technique n’est pas toujours possible si les
classes non synchronisées font appel elles-mêmes
à des objets non-protégés auxquelles on ne peut
accéder

R. Grin Java : threads page 125


Collections thread-safe
‰ Le paquetage [Link] du
JDK 5 (voir section suivante) contient des
collections « thread-safe » qui offrent sécurité et
performance en environnement multi-tâche :
ConcurrentHashMap,
CopyOnWriteArrayList,
CopyOnWriteArraySet
‰ Il ne faut les utiliser que lorsqu’il y a des risques
dus à des modifications par plusieurs threads
(moins performantes que les collections
« normales »)
R. Grin Java : threads page 126
Paquetage
[Link]

R. Grin Java : threads page 127


‰ On a vu que la programmation avec des
threads est complexe et que l’on peut
facilement faire des erreurs, même dans les
cas simples
‰ Il faut donc essayer d’utiliser une API de plus
haut niveau qui prend en compte les cas les
plus fréquents de programmation
concurrente, en cachant la complexité
‰ Les versions 5 et suivantes du JDK offrent
des nouvelles classes pour cela
R. Grin Java : threads page 128
API
‰ Le JDK 1.5 a ajouté un paquetage
[Link] qui offre de
nombreuses possibilités, avec de bonnes
performances
‰ Le programmeur n’aura ainsi pas à réinventer
la roue pour des fonctionnalités standards
telles que les exécutions asynchrones, les
gestions de collections accédées par plusieurs
threads (telles que les files d’attentes), les
blocages en lectures/écritures, etc.
R. Grin Java : threads page 129
‰ Ce cours ne fait que survoler quelques
possibilités offertes par cette API
‰ Pour plus de précisions se reporter à la
javadoc de l’API et à ses tutoriels

R. Grin Java : threads page 130


Problèmes avec Runnable
‰ La méthode run ne peut renvoyer une valeur
et elle ne peut lancer d’exceptions contrôlées
par le compilateur
‰ Les valeurs calculées par la méthode run
doivent donc être récupérées par une
méthode de type getValeur() et les
exceptions contrôlées doivent être attrapées
et signalées d’une façon détournée au code
qui a lancé run

R. Grin Java : threads page 131


Nouveau cadre
‰ La nouvelle API fournit un nouveau cadre
pour faciliter et enrichir les possibilités lors du
lancement de tâches en parallèle
‰ L’interface Callable améliore Runnable
‰ Future facilite la récupération des valeurs
calculées en parallèle
‰ Executor et Executors séparent la
soumission de tâches et leur exécution, et
offrent une gestion de pools de threads

R. Grin Java : threads page 132


Considérations techniques
‰ De nouvelles instructions ont été ajoutées aux
processeurs pour faciliter leur utilisation en
environnement multi-cœurs
‰ Par exemple, dans les processeurs Intel,
l’instruction « compare-and-swap » (CAS), en
une seule opération atomique, (la main ne
peut être donnée à un autre thread pendant
son exécution) compare la valeur d’un
emplacement mémoire à une valeur donnée
et, selon le résultat de la comparaison, modifie
un emplacement mémoire
R. Grin Java : threads page 133
Considérations techniques
‰ La JVM a été adaptée pour bénéficier des ces
nouvelles instructions
‰ La nouvelle API de [Link]
s’appuie sur ces nouveautés pour améliorer la
gestion du multitâche, en particulier pour
améliorer les performances, par rapport à
l’utilisation de synchronize

R. Grin Java : threads page 134


Interface Callable<V>
‰ Elle représente une tâche à exécuter (par
ExecutorService), qui renvoie une valeur
de type V
‰ Une seule méthode qui exécute la tâche :
V call() throws Exception
‰ La classe Executors contient des méthodes
pour envelopper les anciennes interfaces
Runnable, PrivilegedAction et
PrivilegedExceptionAction, et les
transformer en Callable
R. Grin Java : threads page 135
Interface Executor
‰ Représente un objet qui exécute des tâches
qu’on lui a soumises
‰ La soumission d’une tâche est effectuée par
la seule méthode de l’interface :
void execute(Runnable tâche)

R. Grin Java : threads page 136


Exécution des tâches
‰ Les tâches soumises à un exécuteur ne sont
pas nécessairement exécutées par des
threads mais c’est le plus souvent le cas
‰ Au contraire de la classe Thread qui lance
immédiatement un thread avec la méthode
start(), un exécuteur peut choisir le
moment et la manière d’exécuter les tâches
qu’on lui a soumises (dépend de
l’implémentation de la méthode execute)

R. Grin Java : threads page 137


Interface Future<V>
‰ Représente le résultat d’une tâche exécutée
en parallèle
‰ Les méthodes de l’interface permettent de
retrouver le résultat du travail (get),
d’annuler la tâche (cancel) ou de savoir si la
tâche a terminé son travail (isDone) ou a été
annulée (isCancelled)

R. Grin Java : threads page 138


Méthodes get
‰ V get()
récupère le résultat du travail ; bloque si le
travail n’est pas encore terminé
‰ V get(long délai, TimeUnit unité)
idem get() mais ne bloque pas plus
longtemps que le délai passé en paramètre
‰ Exemple :
get(50L, [Link])
‰ Une éventuelle exception durant l’exécution
de la tâche est enveloppée dans une
ExecutionException (getCause pour la
récupérer)
R. Grin Java : threads page 139
Interface ExecutorService
‰ ExecutorService est une sous-interface
de Executor
‰ Elle ajoute des méthodes pour gérer la fin de
l’exécution des tâches (méthodes
« shutdown ») et récupérer un Future
comme résultat de la soumission d’une tâche
(méthodes submit)

R. Grin Java : threads page 140


Méthodes submit (1/2)
‰ Elles enrichissent la méthode execute de
Executor
‰ Elles soumettent une tâche t (Callable ou
Runnable) et retournent un Future qui
représente l’exécution de la tâche
‰ Une éventuelle exception lancée par la tâche
est enveloppée dans une
ExecutionException qui sera renvoyée
par la méthode get de Future

R. Grin Java : threads page 141


Méthodes submit (2/2)
‰ <T> Future<T> submit(Callable<T> t)
‰ 2 méthodes avec Runnable en paramètre (au
lieu de Callable) ; elles renvoient un Future
dont la méthode get renvoie la valeur null ou
une certaine valeur passée en paramètre de
submit, après la fin de l’exécution (car run
d’un Runnable ne renvoie pas de valeur)

R. Grin Java : threads page 142


Méthodes invoke
‰ invokeAll attend la fin de l’exécution (correcte
ou avec exception) d’une collection de tâches
(Callable) et renvoie la liste des résultats
(Future) de toutes les tâches
‰ invokeAny : attend la fin de l’exécution correcte
(sans exception) d’une des tâches
‰ Un timeout peut être passé en paramètre au-delà
duquel les tâches non terminées sont arrêtées
‰ Signature pour invokeAll :
<T> List<Future<T>> invokeAll(
Collection<? extends Callable<T>> tasks)
throws InterruptedException
R. Grin Java : threads page 143
Méthodes shutdown
‰ Méthodes shutdown et shutdownNow pour
arrêter l’activité de l’exécuteur, d’une façon
« civilisée » (avec shutdown les tâches en
cours se terminent normalement ; aucune
nouvelle tâche ne sera acceptée) ou brutale
(avec shutdownNow les tâches en cours
d’exécution sont stoppées) ; il faut appeler
une des 2 méthodes pour que la JVM se
termine à la fin de l’application
‰ List<Runnable> shutdownNow()
renvoie la liste des tâches qui étaient en
attente d’exécution
R. Grin Java : threads page 144
Méthodes liées à shutdown
‰ boolean isShutdown() indique si
l’exécuteur a été arrêté
‰ boolean isTerminated() indique si
toutes les tâches en cours d’exécution au
moment de l’arrêt ont terminé leur exécution
‰ boolean awaitTermination(long
timeout, TimeUnit unité) bloque en
attendant la fin de l’exécution des tâches
après un shutdown, ou après un délai
maximum d’attente (retourne true si le délai
n’a pas été atteint)
R. Grin Java : threads page 145
Exemple de la javadoc
void fermerPool (ExecutorService pool) {
[Link](); // Empêchent de nouvelles tâches
try {
if (![Link](60, [Link])) {
[Link]();
if (![Link](60, [Link]))
[Link]("Tâches pas terminées");
}
} catch (InterruptedException ie) {
[Link]();
[Link]().interrupt();
}
}
R. Grin Java : threads page 146
Interface
ScheduledExecutorService
‰ Hérite de ExecutorService
‰ 4 méthodes « schedule » qui créent un
exécuteur et lancent les exécutions
‰ Permet de donner un délai avant l’exécution
ou de faire exécuter périodiquement une
tâche (comme les timers)
‰ On peut indiquer une périodicité moyenne
pour les exécutions ou un intervalle fixe entre
la fin d’une exécution et le début de la
prochaine exécution
R. Grin Java : threads page 147
Classe Executors
‰ Contient des méthodes static utilitaires
pour d’autres interfaces du paquetage :
Executor, ExecutorService,
Callable,…

R. Grin Java : threads page 148


Méthodes static « callable »
‰ Les méthodes callable renvoient un
Callable qui enveloppe un Runnable,
PrivilegedAction ou
PrivilegedExceptionAction
‰ privilegedCallable(Callable) est
réservée à un appel dans la méthode
doPrivileged de la classe
AccessController ; elle renvoie un
Callable dont les autorisations ne
dépendent pas du contexte d’appel (voir
cours sur la sécurité en Java)
R. Grin Java : threads page 149
Les fabriques (1)
‰ Noms des méthodes commencent par « new »
‰ Une fabrique de Thread
([Link]) peut être
passée en paramètre si on veut des types
spéciaux de threads (par exemple avec une
certaine priorité, ou d’une certaine sous-classe
de Thread)

R. Grin Java : threads page 150


Les fabriques (2)
‰ newCachedThreadPool : crée un pool de
threads réutilisables
n Les threads non utilisés depuis 1 minute
sont supprimés
n Nouveaux threads créés si nécessaire

‰ newFixedThreadPool : crée un pool


contenant un nombre fixe de threads
n Nombre de threads passés en paramètre

R. Grin Java : threads page 151


Les fabriques (3)
‰ newSingleThreadExecutor : crée un
exécuteur qui exécutera les tâches les unes
après les autres
‰ newScheduledThreadPool : crée un pool
de threads qui peuvent exécuter des
commandes après un certain délai ou
périodiquement
‰ newSingleThreadScheduledExecutor :
crée un exécuteur qui peut exécuter des
commandes après un certain délai ou
périodiquement, une commande à la fois
R. Grin Java : threads page 152
Exemple de pool avec Runnable
ExecutorService pool =
[Link](10);
Runnable tache = new Runnable() {
public void run() {
...
}
};
[Link](tache);

R. Grin Java : threads page 153


Exemple de pool avec Callable
ExecutorService pool =
[Link](10);
Callable<Integer> tache =
new Callable<Integer>() {
public Integer call() {
...
}
};
Future<Integer> f = [Link](tache);
...
int v = [Link]();
R. Grin Java : threads page 154
‰ Les quelques transparents suivants
présentent les nouvelles collections thread-
safe

R. Grin Java : threads page 155


Classe ConcurrentHashMap
‰ Au contraire de HashTable, les lectures ne
bloquent ni les autres lectures ni même les
mises à jour
‰ Une lecture reflète l’état de la map après la
dernière mise à jour complètement terminée
(celles en cours ne sont pas prises en
compte)
‰ Les méthodes sont à peu près les mêmes
que celles de HashMap

R. Grin Java : threads page 156


Classe
CopyOnWriteArrayList
‰ Variante « thread-safe » de ArrayList dans
laquelle toutes les opérations de mise à jour
sont effectuées en faisant une copie du tableau
sous-jacent (le tableau n’est jamais modifié)
‰ Cette variante n’est intéressante que lorsque
les parcours de la liste sont bien plus fréquents
que les mises à jour
‰ Les itérateurs reflètent la liste au moment où ils
ont été créés ; ils ne peuvent modifier la liste
‰ Idem pour CopyOnWriteArraySet
R. Grin Java : threads page 157
Interfaces pour files d’attente
‰ L’interface [Link], introduite par
le JDK 1.5, est semblable à List mais
correspond à une file d’attente : elle ne
permet des ajouts qu’à la fin et des
suppressions qu’au début
‰ Cette contrainte permet plus d’optimisation
que pour les implémentations de List

R. Grin Java : threads page 158


Interfaces pour files d’attente
‰ L’interface
[Link]
hérite de Queue et ajoute une méthode qui se
bloque lorsqu’elle veut supprimer un élément
dans une file vide (take) et une méthode qui
se bloque lorsqu’elle ajoute un élément dans
une file pleine (put)
‰ Elle permet d’implémenter des files pour des
producteurs et des consommateurs

R. Grin Java : threads page 159


Classes pour files d’attente
‰ BlockingQueue est implémenté par les
classes
n LinkedBlockingQueue

n PriorityBlockingQueue

n ArrayBlockingQueue

n SynchronousQueue

R. Grin Java : threads page 160


Synchronisation des threads
‰ La nouvelle API de concurrence offre des
objets pour faciliter le codage des
programmes qui utilisent plusieurs traitement
en parallèle
‰ La classe Semaphore implémente un
sémaphore classique
‰ Les classes CountDownLatch et
CyclicBarrier représentent des structures
un peu plus complexes qui facilitent la
synchronisation de plusieurs threads
R. Grin Java : threads page 161
Semaphore

‰ Un sémaphore gère des ressources limitées


‰ La méthode public void acquire()
demande une ressource et bloque jusqu’à ce
qu’il y en ait une disponible ; lorsque la
méthode retourne, une ressource en moins
est disponible via le sémaphore
‰ La méthode public void release()
rend une ressource ; lorsque la méthode
retourne, une ressource de plus est
disponible via le sémaphore
R. Grin Java : threads page 162
Semaphore

‰ Plusieurs variantes des méthodes de base


acquire et release sont disponibles (pour
acquérir ou rendre plusieurs ressources en
même temps ou pour ne pas se bloquer en
attente de ressources)

R. Grin Java : threads page 163


CountDownLatch et
CyclicBarrier
‰ Les classes CountDownLatch et
CyclicBarrier facilitent la décomposition
d’un traitement en plusieurs sous-traitements
parallèles
‰ Cas d’utilisation : à certains points d’exécution
le traitement ne peut se poursuivre que si les
sous-traitements ont terminé une partie de leur
travail
‰ La classe Phaser, introduite par le JDK 7, est
plus souple que ces 2 classes (voir javadoc)
R. Grin Java : threads page 164
CyclicBarrier
‰ Représente une barrière derrière laquelle n
threads (n est un paramètre du constructeur) qui
représentent les sous-traitements, attendent par
la méthode [Link]()
‰ Quand les n threads y sont arrivés, la barrière
« se lève » et les threads continuent leur
exécution
‰ La barrière est « cyclic » car elle se rabaisse
ensuite, et les threads y sont à nouveau bloqués
s’ils attendent par [Link]()
R. Grin Java : threads page 165
CyclicBarrier
‰ Un constructeur permet de passer en
paramètre un Runnable qui sera exécuté juste
avant que la barrière ne se relève

R. Grin Java : threads page 166


CountDownLatch
‰ Rôle similaire à une barrière mais il ne peut
servir qu’une seule fois et le fonctionnement
est différent : l’arrivée au point d’attente est
dissociée de l’attente elle-même
‰ Le constructeur prend en paramètre un
nombre n
‰ Les sous-traitements peuvent décrémenter
ce nombre par [Link]()
‰ Des threads peuvent aussi attendre que n
soit égal à 0 par [Link]()
R. Grin Java : threads page 167
CountDownLatch
‰ Peut être utilisé pour le même usage qu’une
barrière : les sous-traitements appellent
countDown et await juste après
‰ Mais aussi comme un starter : n est initialisé à
1 et tous les threads commencent par
attendre par await ; ensuite un thread
« starter » appelle countDown pour faire
démarrer tous les threads

R. Grin Java : threads page 168


Phaser
‰ La classe Phaser, introduite par le JDK 7,
étend les possibilités de CountDownLatch et
CyclicBarrier, par exemple (voir javadoc
pour plus d’information) :
‰ Des nouvelles tâches peuvent être
enregistrées en cours de traitement
‰ Un traitement différent peut être exécuté à
l’arrivée d’une tâche et lorsqu’elle attend
‰ On peut forcer une fin d’exécution
‰ Un phaser peut être surveillé de l’extérieur

R. Grin Java : threads page 169


D’autres cas de « happens-before »
‰ L’ajout d’un objet dans une collection « thread-
safe » de cette API happens-before (voir section
« Difficultés du multi-tâche) un accès ou une
suppression de l’objet dans la collection
‰ Les actions effectuées pour le calcul d’un
Future happens-before la récupération de la
valeur du Future
‰ Une action exécutée par un thread avant une
attente à une barrière cyclique (await) happens-
before l’action (optionnelle) exécutée par la
barrière au moment où elle se lève, qui elle-
même, happens-before une action qui suit await
R. Grin Java : threads page 170
Fork - join

R. Grin Java : threads page 171


Présentation
‰ Le JDK 7 fournit le framework « fork-join » qui
facilite la programmation avec des tâches qui
peuvent s’exécuter en parallèle, dans le cas
fréquent où une tâche peut se décomposer
récursivement en sous-tâches de même nature
‰ Les sous-tâches sont lancées de manière
asynchrone (fork) et on attend leur exécution
(join) pour combiner leur résultat

R. Grin Java : threads page 172


Exemple
‰ Un tri fusion d’une liste ou d’un tableau :
chaque processeur se charge de la moitié des
valeurs à trier et ensuite les 2 parties triées
sont fusionnées
‰ Chacune des 2 parties peut elle-même être
récursivement partagées en 2 parties qui
seront fusionnées, et ainsi de suite
‰ Lorsque les parties sont assez petites, on les
trie directement, sans les décomposer en 2,
ce qui arrête la descente récursive
R. Grin Java : threads page 173
Le plus du framework « fork-join »
‰ Le nombre de processeurs est limité ; le
framework fork-join attribue par défaut autant de
threads/exécuteurs que de processeurs pour
accomplir la tâche principal (possible de donner
un autre nombre dans le constructeur de
ForkJoinPool)
‰ Les sous-tâches sont attribuées aux exécuteurs
au fur et à mesure qu’elles sont créées
(ajoutées à une collection de type Deque )
attachée à chaque exécuteur)
R. Grin Java : threads page 174
Le plus du framework « fork-join »
‰ Le framework gère les exécuteurs de telle sorte
qu’ils chôment le moins souvent possible :
n Quand un exécuteur rencontre un join, il exécute une
autre tâche (au lieu de se mettre en attente)
n Quand un exécuteur n’a plus de tâche à exécuter, il
peut aller « voler » une tâche d’un autre exécuteur
pour l’exécuter
‰ S’il n’y a rien à voler, il se « repose »
momentanément en attendant l’arrivée d’une
nouvelle tâche à accomplir

R. Grin Java : threads page 175


Classes
‰ 2 classes du paquetage
[Link] permettent de
programmer ce genre de tâche :
n ForkJoinPool, une extension de
AbstractExecutorService qui
implémente l’algorithme de division des
tâches en 2 sous-tâches
n ForkJoinTask<V> classe abstraite qui

représente la tâche à exécuter (implémente


Future<V>) ; version « légère » d’un thread
R. Grin Java : threads page 176
ForkJoinPool
‰ Constructeur
ForkJoinPool(int nbThreads)
si on ne passe pas de paramètre, prend par
défaut le nombre de processeurs de la machine
sur laquelle le programme s’exécute (donné par
[Link]())
‰ Méthode
<T> T invoke(ForkJoinTask<T> tâche)
lance la tâche (qui sera décomposée en sous-
tâches)
R. Grin Java : threads page 177
2 types de tâche
‰ Héritent de ForkJoinTask<V>
‰ RecursiveTask<V> dont la méthode
compute retourne une valeur (de type V)
‰ RecursiveAction dont la méthode
compute ne retourne aucune valeur (hérite
de ForkJoinTask<Void>)
‰ Le développeur doit écrire le code à exécuter
dans chaque sous-tâche dans une classe fille
d’une de ces classes, dans la méthode
protected compute() qui retourne V ou
void suivant le type de tâche
R. Grin Java : threads page 178
Méthodes principales de
ForkJoinTask
‰ fork() : exécute la tâche de façon
asynchrone
‰ V join() : attend la fin de son exécution et
retourne la valeur calculée par la tâche
‰ invokeAll(ForkJoinTask<?> t1,
ForkJoinTask<?> t2) (méthode
surchargée ; voir javadoc) : lance les tâches
t1 et t2 de façon asynchrone et attend leur
fin (le plus simple ; cache l’utilisation de fork
et join)
R. Grin Java : threads page 179
Exemple schématique
compute() {
if (portion du travail est petite) {
faire le travail directement
}
else {
diviser le travail en sous-tâches
invokeAll(sous-tâche1, sous-tâche2,...)
composer les résultats des sous-tâches
}
}

R. Grin Java : threads page 180


Etat des tâches
‰ La classe ForkJoinTask contient des
méthodes qui peuvent être utilisées pour avoir
l’état de la tâche :
n isDone() retourne true si la tâche est terminée
(déroulement normal, exception ou annulation)
n isCancelled() retourne true si la tâche a été
annulée avant d’avoir terminé son exécution normale
n isCompletedNormally() retourne true si la tâche
s’est terminée sans exception ni annulation
n getException() retourne un Throwable lancé
par l’exécution de la tâche, une
CancellationException, ou null si tout s’est
bien passé ou si la tâche n’est pas terminée
R. Grin Java : threads page 181
Exemple de RecursiveAction
‰ Incrémenter les valeurs d’un tableau (exemple
de la javadoc de RecursiveAction) :
class IncrTask
extends RecursiveAction {
final long[] t; final int deb;
final int fin;
IncrTask(long[] t, int deb, int fin) {
this.t = t; [Link] = fin;
[Link] = fin; }
// suite prochain transparent...

R. Grin Java : threads page 182


Exemple de RecursiveAction
protected void compute() {
if (fin – fin < SEUIL) {
for (int i = deb; i < fin; ++i)
t[i]++;
}
else {
// « Division par 2 »
int milieu = (deb + fin) >>> 1;
invokeAll(
new IncrTask(t, deb, milieu),
new IncrTask(t, milieu, fin));
}
}
}
R. Grin Java : threads page 183
Lancement de l’exécution
ForkJoinPool pool = new ForkJoinPool();
[Link](new IncrTask(t));

R. Grin Java : threads page 184


Exemple de RecursiveTask
‰ Calcul des nombres de Fibonacci (exemple de
la javadoc de RecursiveTask) :
class Fibonacci
extends RecursiveTask<Integer> {
final int n;
Fibonacci(int n) { this.n = n; }
public Integer compute() {
if (n <= 1) return n;
Fibonacci f1 = new Fibonacci(n - 1);
[Link]();
Fibonacci f2 = new Fibonacci(n - 2);
return [Link]() + [Link]();
}
}
R. Grin Java : threads page 185
Lancement de l’exécution
ForkJoinPool pool = new ForkJoinPool();
[Link](new Fibonacci(30));

R. Grin Java : threads page 186

Vous aimerez peut-être aussi