Java: Maîtriser les Threads et Runnable
Java: Maîtriser les Threads et Runnable
Threads en Java
1
Concepts
• Thread:
– Unité de programme qui est exécuté en parallèle
avec le reste des programmes
• Processus en parallèle
– Plusieurs processus sont exécutés en même
temps
• Créer, lancer et terminer un thread
• Interface Runnable
• SynchronisaHon des threads
2
Interface Runnable
• Une méthode run() exigée:
public interface Runnable
{
void run();
}
• ImplantaHon:
public class MyRunnable implements Runnable
{
public void run()
{
// Task statements go here
. . .
}
}
3
Créer un thread
Un Thread est une classe qui implante Runnable
1. Créer une sous-classe de Thread (doit aussi implanter run() )
class PrimeThread extends Thread {
long minPrime;
PrimeThread(long minPrime) {
[Link] = minPrime;
}
public void run() {
// compute primes larger than minPrime
. . .
}
}
UHlisaHon:
PrimeThread p = new PrimeThread(143);
[Link]();
4
Créer un thread
2. Un thread est créé à base d’un objet
Runnable
5
Classe Thread
public class Thread extends Object
implements Runnable
Constructeurs:
Thread()
Thread(Runnable target)
Thread(String name)
…
6
Classe Thread
• Méthodes:
sta,c int ac,veCount(): nb. de threads de ce groupe
void destroy()
void interrupt()
sta,c void sleep(long millis)
void start()
sta,c void yield(): faire une pause et perme_re aux autres
threads d’exécuter (traitement de deadlock)
…
7
ExplicaHon de thread
Séquence d’acHons
• Bien ordonnée en parallèle
8
Types de threads
Les threads utilisateurs se terminent lorsque les instructions dans le corps de leur
méthode run ( ) sont toutes exécutées.
Un thread démon continue indéfiniment si aucune précaution n’a été prise pour
l’arrêter.
Nous commencerons par étudier les threads dits utilisateurs pour terminer sur
un bref aperçu portant sur les threads démons.
9
États d’un Thead
10
Détecter l’état d’un Thread
• [Link]():
– NEW : lors de sa créaHon
– RUNNABLE: lorsqu'on invoque la méthode start(),
le thread est prêt à travailler.
– BLOCKED : lorsque le thread a effectué toutes ses
tâches ; on dit aussi qu'il est « mort ».
– WAITING : lorsque le thread est en a_ente
indéfinie.
– TIMED_WAITING: : lorsque le thread est en pause
(quand vous uHlisez la méthode sleep(), par
exemple).
– TERMINATED
11
Constructeurs de Thread
12
Méthodes de la classe Thread
14
Premier Thread avec [Link]
L’usage de la méthode statique sleep (long millis) nous permet de voir que les deux
threads s’exécutent en apparente simultaneité.
Cette méthode peut lever une exception de type InterruptedException qu’il faut donc
intercepter et capturer.
La méthode start ( ) ne peut être appelée q’une seule fois pour un thread donné, sinon
une exception de type IllegalThreadStateException est levée.
Il est possible d’appeler la méthode run ( ) pour chaque thread mais cela entraîne l’
exécution complète du thread 1 puis celle complète du thread 2. l’appel de sleep
entraînerait alors l’exécution d’autres threads autres que ceux –ci, donc ralentissement
de l’exécution de notre programme.
16
Deuxième Thread avec [Link]
(le premier exemple réalisé ici avec l’interface Runnable)
Si un thread est bloqué, il ne peut pas déterminer s’il est interrompu. C’est à ce moment
que la classe InterruptedException intervient. Lorsque la méthode interrupt est appelée
sur un thread bloqué, l’appel bloquant (comme sleep ou wait) est terminé par une
InterruptedException.
Pour savoir si un thread est couramment actif c’est-à-dire qu’il est soit exécutable, soit
bloqué, utilisez la méthode boolean isAlive ( ). Elle renvoie true si le thread est
exécutable ou bloqué et false si le thread est nouveau et pas encore exécutable ou si le
thread est mort.
20
[Link]
Par défaut un thread appartient (est créé) au groupe (de threads) courant càd
celui qui l’a créé.
Il faut savoir que le premier thread que l’on rencontre est la méthode main.
Par défaut donc, un thread est créé dans ce groupe.
Mais il est possible de créer des groupes de threads autre que le groupe courant, et à
chacun, associé un certain nombre de threads.
Dans ce cas, il sera plus facile d’interrompre un ensemble de threads, en
interrompant simplement le groupe.
Pour cela, on crée une instance de la classe ThreadGroup avec l’un des constructeurs:
Un thread démon est un thread qui n’a aucun autre but que de servir d’autres threads.
L’exécution de tels threads peut se poursuivre même après l’arrêt de l’application
qui les a lancés. On dit qu’ils s’exécutent en tâche de fond.
Une application dans laquelle les seuls threads actifs sont des démons est
automatiquement fermée.
Un thread doit toujours être créé comme thread standard, puis il peut être
transformé en thread démon grâce à un appel de la méthode setDaemon ( true).
Mais cet appel doit se faire avant le lancement du thread sinon une exception
de type IllegalThreadStateException est levée.
Un thread démon dépend du thread parent qui l’a lancé et s’exécute en arrière plan
de ce dernier.
22
SynchronisaHon
+2 X -100
*1.02 -20
Accès conflictuels:
écrire lire
(put - producer) (get -consumer)
23
SynchronisaHon pour un bloc
24
Premier exemple SynchronisaHon de bloc (1/5)
Considérons une classe Compte permettant de gérer les comptes de clients dans une
quelconque banque disposant de plusieurs agences.
Pour une gestion efficace et sécurisée des comptes client, il ne faudrait pas permettre
par exemple que deux (au moins) transactions s’effectuent simultanément sur un même
compte à un instant donné (exemple: le retrait et le dépôt).
Les opérations de retrait et de dépôt sont gérées séparément par deux threads distincts.
Notre travail sera de créer ces deux threads de telle sorte qu’ils ne pourront jamais s’
exécuter simultanément sur un même compte.
25
Premier exemple SynchronisaHon de bloc (2/5)
([Link])
package [Link];
public class Compte {
private double solde;
private double decouvert;
public Compte(double solde, double decouvert){
[Link] = solde;
[Link] = decouvert;
}
public void deposer (double montant){
[Link] += montant;
}
public void retirer (double montant){
if (montant + decouvert <= solde)
[Link] -= montant;
}
public double getSolde ( ) {
return solde ;
26
}}
Premier exemple SynchronisaHon de bloc (3/5)
(ThreadCompteDepot .java)
package [Link];
public class ThreadCompteDepot extends Thread {
private Compte c;
private double depot;
public ThreadCompteDepot (String name, Compte c, double depot){
super (name);
this.c =c;
[Link] = depot;
}
public void run ( ){
synchronized (c) { // fondamental: on pose un verrou sur le compte
try {
[Link] ([Link] ( ) + ":avant le depot: "+[Link]( ));
[Link] ([Link]);
[Link] (1000);
[Link] ([Link]() + ":apres le depot: "+[Link]( )); ;
}
27
catch (InterruptedException e) { [Link]("retrait avorte"); } } }}
Premier exemple SynchronisaHon de bloc (4/5)
(ThreadCompteRetrait .java)
package [Link];
public class ThreadCompteRetrait extends Thread {
private Compte c;
private double retrait;
public ThreadCompteRetrait (String name, Compte c, double retrait){
super (name);
this.c = c;
[Link] = retrait;
}
public void run ( ){
synchronized (c) { // fondamental: on pose un verrou sur le compte
try {
[Link] ([Link] ( ) + ":avant le retrait:"); S.O.P([Link] ( ));
[Link] ([Link]);
[Link] (1000);
[Link] ([Link]() + ":apres le retrait:"); S.O.P([Link] ( ));
}
28
catch (InterruptedException e) { [Link] (" depot avorte"); } } }}
Premier exemple SynchronisaHon de bloc (5/5)
(TestThreadsCompte .java)
retrait:avant le retrait votre solde est: 5000.0 Si vous ne synchroniser pas le Compte
depot:avant le depot: votre solde est: 3000.0 dans les deux threads, vous aurez un
retrait: apres le retrait votre solde est: 4500.0 solde erroné .
depot:apres le depot: votre solde est: 4500.0
Mr NDONG. FST/UCAD 29
Deuxième Exemple de bloc synchronisé
On considère une classe qui permet d’inverser les éléments d’un tableau d’entiers.
Mais avant que l’inversion ne se fasse, les éléments du tableau doivent être incrémentés
d’une valeur égale à l’indice de l’élément en cours. Et après, l’inversion pourra se faire
après un certain délai.
On disposera d’une méthode affiche qui nous permettra d’envoyer sur la console
les éléments du tableau inversé.
Mais ici, nous avons un problème à gérer:
Avant que la méthode affiche n’accède au tableau pour l’afficher, il faudra que la
méthode qui se charge de l’incrémentation et de l’inversion finisse carrément son
travail, sinon l’affichage sera complètement faux.
Regardons maintenant comment on peut passer d’un mauvais exemple vers un cas où
les données seront correctes.
30
Exemple de bloc NON synchronisé (1/4)
class TabInverse {
int t [ ];
int [ ] inverse (int tableau [ ])
{ t = new int [[Link] ];
/*cette classe permet de créer un thread qui n’accédera qu’à la methode inverse
Cet objet peut donc manipuler simultanément le tableau qu’un autre thread */
class EssaiSynchroInverse extends Thread{
TabInverse inv;
int tab[ ];
public EssaiSynchroInverse (String name, TabInverse inv, int tab [ ] )
{ super (name);
[Link] = inv;
[Link] = tab;
}
public void run ( )
{[Link] .println ([Link] ( ) ) ;
[Link] (tab) ;
try {[Link] (1000) ;}
catch (InterruptedException er) { }
[Link] .println("FIN de "+[Link] ( ) ) ;
}} 32
Exemple de bloc NON synchronisé (3/4)
/*cette classe permet de créer un thread qui n’accédera qu’à la methode affiche
Cet objet peut donc manipuler simultanément le tableau qu’un autre thread */
class EssaiSynchroAffiche extends Thread{
TabInverse inv;
int tab[ ];
public EssaiSynchroAffiche (String name, TabInverse inv, int tab[] )
{ super (name);
[Link] = inv;
[Link] = tab;
}
public void run ( )
{ [Link] .println ([Link] ( ) ) ;
[Link] ( ) ;
try {[Link](1000) ;}
catch (InterruptedException er) { }
[Link] .println ("FINITION de "+ [Link] ( ) ) ;
}}
33
Exemple de bloc NON synchronisé (4/4)
Pour corriger ce défaut, il faut poser un verrou sur l’objet tableau partagé et sur inv…34
Deuxième Exemple de bloc synchronisé
Chaque fois que deux threads s’exécutent en même temps, il faut souvent prendre des
mesures adéquates pour qu’ ils n’accèdent pas simultanément à une même variable.
Le principe d’exclusion mutuelle doit être assuré sur le partage simultané d’objet
(pour assurer la cohérence des données) par l’utilisation de méthodes dites synchronisées.
Ce principe est assuré lorsqu’une méthode est déclarée avec le mot clé synchronized.
Une méthode synchronisée appartient à un objet quelconque, pas forcément à un thread.
Lorsqu’une méthode déclarée synchronized, est en cours d’exécution par un thread, tous
les autres threads qui en auraient besoin doivent attendre la fin de son exécution.
Lorsque synchronized est utilisé comme modifieur de méthode d’instance, une instruction
telle que: synchronized void method ( ) {…….} est équivalente à:
void method ( ) {
synchronized (this) { ….}
}
36
Exemple de SynchronisaHon de méthodes (1/4)
Regardons d’abord comment les valeurs des deux champs sont érronées et incohérentes si
l’exclusion mutuelle n’est pas bien gérée: l’incohérence s’explique par le fait que le s
trois threads manipulent les deux champs pèle mêle.
Nous verrons alors une version qui dégage une utilisation correcte de la valeur de ces
variables.
37
Exemple de SynchronisaHon de méthodes (2/4)
class Synchro {
int n, som;
public Synchro (int n,int som) { this.n =n; [Link] =som;
}
void addition ( ){ // methode non synchronisée
[Link] .print ("n++= "+(n++) +" suivi de ") ;
try { [Link] (222) ;}
catch (InterruptedException t){ }
som += n; [Link] .println(" et som="+som) ;
}
void affiche ( ){ // methode non synchronisée
[Link] .print("affiche: n= " +(++n)+" et ");
try {[Link] (222) ;}
catch (InterruptedException t){ }
[Link] ("affiche :som= "+som);
}}
38
Exemple de SynchronisaHon de méthodes (3/4)
}
}
40
SorHe de l’ exemple
Attention: c’est des méthodes de la super classe Object et non de la classe Thread.
Elles implantent des mécanismes de demande de verrou et d’ avertissement de libération
de ce verrou.
une méthode synchronisée peut appeler la méthode wait( ) de l’objet dont elle possède
le verrou, pour:
- rendre le verrou à l’environnement qui peut alors le donner à une autre méthode
synchronisée,
- mettre en attente le thread correspondant.
Une méthode synchronisée peut appeler la méthode notifyAll ( ) d’un objet afin de
prévenir tous les threads en attente sur cet objet et de leur donner la possibilité de
s’exécuter.
NB: notifyAll( ) permet de débloquer un wait ( ) sur un objet où notify( ) a été lancé.
La méthode notify ( ) prévient un seul thread. Avant de faire un notify ( ) ou notifyAll()
il faut changer la condition de boucle qui mène au wait ( ).
43
Exemple: producteur - consommateur. (1/4)
Un producteur est un thread qui dépose des jetons numérotés dans un chapeau
qui ne peut contenir qu’un seul jeton. Un consommateur prend ce jeton qui doit
être présent dans le chapeau. Donc:
- le producteur doit s’arrêter de déposer des jetons lorsqu’il y en a déjà un et doit
être informé qu’un jeton a été retiré.
- le consommateur ne peut pas prendre de jetons s’il n’y en a pas (arrêt du
consommateur) et doit être informé lorsqu’un jeton a été déposé.
L’objet le plus à même pour avertir le producteur et le consommateur est le
chapeau lui-même.
44
Exemple: producteur - consommateur. (2/4)
(fichier [Link])
(fichier [Link])
(fichier [Link])
public class Chapeau {
private int contenu; /*une classe pour tester*/
private boolean permis = false; public class TestProductCons {
public synchronized int get ( ){ public static void main(String [ ] args) {
while (permis == false) Chapeau chap = new Chapeau ( );
{ try {wait ( ); // rendre le verrou } Producteur p = new Producteur(chap,1);
catch (InterruptedException e){ } Consommateur c=
} new Consommateur(chap,1);
permis = false; notifyAll ( ); [Link] ( ) ;
return contenu; } [Link] ( );
public synchronized void put (int value){ }
while (permis == true) }
{ try { wait ( );}
catch (InterruptedException er) { } }
contenu = value; permis = true;
notifyAll ( );// attribuer le verrou à un autre
// qui peut alors s’exécuter
47
Exemple: producteur – consommateur: sorHe
le producteur N° 1 a mis 0
le consommateur N° 1 a pris 0
le producteur N° 1 a mis 1
le consommateur N° 1 a pris 1
le producteur N° 1 a mis 2
le consommateur N° 1 a pris 2
le producteur N° 1 a mis 3
le consommateur N° 1 a pris 3
le producteur N° 1 a mis 4
le consommateur N° 1 a pris 4
48
Exercice: producteur – consommateur
(fichier [Link])
(fichier [Link])
le producteur N° 1 a mis 0
le consommateur N° 1 a pris 0
le producteur N° 2 a mis 0
le consommateur N° 1 a pris 0
le producteur N° 3 a mis 0
le consommateur N° 1 a pris 0
Voici un exemple de sortie le producteur N° 3 a mis 1
du programme de test.
le consommateur N° 1 a pris 1
le producteur N° 3 a mis 2
le consommateur N° 1 a pris 2
le producteur N° 1 a mis 1
le consommateur N° 2 a pris 1
le producteur N° 1 a mis 2
le consommateur N° 2 a pris 2
le producteur N° 1 a mis 3
le consommateur N° 2 a pris 3
le producteur N° 1 a mis 4
………
52
Priorité des Threads
Jusqu’à présent, nous n’avons manipulé que des threads de même priorité.
En théorie, il est permis d’attribuer une certaine priorité à un thread.
Pour cela, on utilise la méthode setPriority (int threadPriority) où le paramètre
transmis en argument est une valeur entière comprise entre [Link]
(correspondant à la valeur 1) et la valeur [Link] (correspondant à 10).
53
Priorité des Threads: Exemple (1/2)
Ce qu’on aurait si les deux threads étaient de même priorité (celle par défaut).
55