Vous êtes sur la page 1sur 16

Chapitre 5 les Threads

Multitâche
C'est le fait de d'exécuter plusieurs taches (ou processus) simultanément et cela sans conflit.
Par exemple écouter de la musique et lire son courrier en même temps.
Un système monoprocesseur simule le multitâche en attribuant un temps (un quantum ou
temps déterminé à l'avance) de traitement à chaque processus.

Un programme est dit multitâche s'il est capable de lancer plusieurs parties de son code en
même temps. À chaque partie du code sera associé un sous processus pour permettre
l'exécution en parallèle.

Exemple : La possibilité de lire l'aide en ligne et d'imprimer à la fois, sous word. Deux sous-
processus, d'un processus qui est associé à l'application word.

Thread
On peut appliquer la multiprogrammation au sein d'un même programme. On dit dans ce cas
que le programme est formé de plusieurs threads indépendants.
Le contrôle d'exécution des threads se fait au niveau de ce programme. Ces threads peuvent
communiquer entre eux et partager des données.

Un thread est un sous-ensemble d'un processus, partageant (ses ressources) son espace
mémoire et ses variables. De ce fait, les coûts associés suite à son lancement sont réduits,
donc plus rapide.

Cependant le partage de la mémoire et les variables du processus entraînent un certain nombre


de problèmes, quand par exemple il y a un accès partagé à une ressource.

Exemple : Deux agents (threads) qui veulent effectuer des réservations de places d'avion
(ressource: nombre de places disponibles dans un avion).

Comme solution, on doit protéger l'accès à cette ressource dès qu'un thread est entrain de
réaliser une écriture (réaliser une réservation) sur cette ressource.

Création d'un thread


Pour créer un thread nous avons besoin de décrire le code qu'il doit exécuter et le démarrer
par la suite
Un thread est une classe qu'on peut créer de deux façons : en héritant de la classe Thread ou
en créant une classe implémentant l'interface Runnable.

Classe qui dérive de java.lang.Thread


La première méthode pour créer un thread consiste à créer une classe qui hérite de la classe
Thread. Cette classe doit surcharger la méthode run() de la classe Thread.

Syntaxe
class MonThread extends Thread {
MonThread() {
... code du constructeur ...
}

1
public void run() {
... code à exécuter dans le thread ...
}
}

Lancement d'un thread


initialisation du thread : méthode start()
Un thread dispose d'une méthode nommée run. Cette méthode sera exécutée lorsque le thread
est démarré.

Le lancement de l'exécution du thread se fait en appelant la méthode Start de la classe Thread.

Exemple
Class nom_thread extends Thread;
ecrit e =new nom_thread(….)
e.start();

L'appel de la méthode start() passe le thread à l'état "prêt'', ce qui lui permet de démarrer
dès que possible (mais pas nécessairement immédiatement).

C'est la machine virtuelle JAVA qui décide du moment auquel le thread va s'exécuter. Lorsque
le thread démarre, JAVA appelle sa méthode run().

Un thread se termine lorsque sa méthode run() se termine.

Pour interrompre de temps en temps la méthode run on peut utiliser par exemple la méthode
sleep(t) où t est le nombre de milisecondes.

Exemple
Programme qui lance trois threads permettant d'afficher :
• 10 fois le mot "bonjour" par le premier thread.
• 12fois le mot "bonsoir" par le deuxième thread.
• 5 fois un changement de ligne par le troisième thread.

class ecrit extends Thread {


public ecrit(String texte, int nb, long attente)
{
this.texte=texte;
this.nb=nb;
this.attente=attente;
}
public void run()
{
try{
for(int i=0;i<nb;i++)
{
System.out.print(texte);
sleep(attente);
}
}catch(InterruptedException e){}

2
}
private String texte;
private int nb;
private long attente;
}

public class threed1{


public static void main(String args [])
{
ecrit e1=new ecrit ("bonjour",10,5);
ecrit e2=new ecrit ("bonsoir",12,10);
ecrit e3=new ecrit ("\n",5,15);
e1.start();
e2.start();
e3.start();
}
}

Utilisation de l'interface Runnable


L'autre moyen pour créer un thread est de déclarer une classe qui implémente l'interface
Runnable. Cette interface déclare seulement une méthode : run().
La classe Thread a un créateur qui prend en argument une instance implémentant Runnable.
La classe se déclare comme dans l'exemple précédent, mais on implémente Runnable au lieu
d'hériter de Thread :
class MonThread implements Runnable {
MonThread() {
... code du constructeur ...
}

public void run() {


... code a executer dans le thread ...
}
}
Pour créer et lancer un thread, on crée d'abord une instance de la classe MonThread, puis une
instance de Thread sur laquelle on appelle la méthode start() :
MonThread p = new MonThread();
Thread T = new Thread(p);
T.start();

Dans notre exemple on utilise la classe ecrit mais cette fois elle doit implémenter l'inerface
Runnable.

class ecrit implements Runnable


ecrit e =new ecrit(….);

Maintenant l'objet e n'est plus un thread et ne peut pas être lancé par la méthode start.
On construit un thread par new Thread ( e); où e est de classe héritant de Runnable.
Thread t = new Thread(e);
t.start(); // appelle le run de la classe de e.

Exemple
class ecrit implements Runnable {

3
public ecrit(String texte, int nb, long attente)
{
this.texte=texte;
this.nb=nb;
this.attente=attente;
}

public void run()


{
try{
for(int i=0;i<nb;i++)
{
System.out.print(texte);
Thread.sleep(attente);
}
}catch(InterruptedException e){}
}
private String texte;
private int nb;
private long attente;
}

public class thread2{


public static void main(String args [])
{
ecrit e1=new ecrit ("bonjour ",10,100);
ecrit e2=new ecrit ("bonsoir ",12,100);
ecrit e3=new ecrit ("\n",5,100);
Thread T1 = new Thread(e1);
Thread T2 = new Thread(e2);
Thread T3 = new Thread(e3);

T1.start();
T2.start();
T3.start();
}
}
Remarque
On peut doter la classe ecrit d'une méthode start pour lancer le thread.
class ecrit implements Runnable
{

Public void start ()
{
Thread t=new Thread(this);
T.start();
}
}

Quelques méthodes de la classe Thread

4
La class Thread offre un certain nombre de méthodes pour contrôler le comportement des
threads.
 void destroy(); // détruit le Thread courrent sans faire le ménage.
 String getName(); // retourne le nom du Thread.
 int getPriority(); // retourne la priorité du Thread.
 void interrupt(); // interrompt le Thread.
 static boolean interrupted(); // teste si le Thread courrent a été interrompu.
 void join(); ou void join(long m); // attendre la mort du Thread, après m millisecondes
 void resume(); // redémarrer le Thread.
 void run(); // La méthode contenant le code à exécuter par le Thread.
 void setPriority(int newPriority); // changer la priorité du Thread.
 static void sleep(long millis); ou static void sleep(long millis, int nanos); // mettre en
veille le Thread pendant millis millisecondes ou millisecondes plus nanosecondes.
 void start(); // démarrer un Thread.
 String toString(); // nom du thread, priorité, et le groupe à qui il appartient.
 isAlive() : retourne vrai si le thread auquel on applique la méthode est vivant
(c'est à dire à été démarré par start() et que sa méthode run() n'est pas encore
terminée. Le thread vivant est donc prêt, bloqué ou en cours d'exécution.

Interruption d'un thread


Un thread peut interrompre un autre à l'aide de la méthode interrupt de la classe Thread.
Cette méthode demande à l'environnement de positionner un indicateur signalant une
demande d'arrêt du thread.
Cet appel n'interrompt pas directement le thread (l'arrêt effectif d'un thread reste sous sa
propre responsabilité).
Il est aussi possible de connaître l'état de cet indicateur à l'aide de la méthode static
interrupted de la classe Thread.

Schéma récapitulatif
Thread2 :
Thread1 : t.interrupt(); run {
//positionne un …
indicateur dans t if(interrupted )
{………
return; // fin du thread
}

Exemple
On reprend le premier exemple où le thread principal main interrompt les trois threads.
Ces threads sont supposés infinis (affichage infini de texte).

class ecrit extends Thread {


public ecrit(String texte, long attente)
{
this.texte=texte;
this.attente=attente;
}
public void run()
{

5
Try
{
while (true)
{
if(interrupted()) return;
System.out.print(texte);
sleep(attente);
}
}catch(InterruptedException e) { return; }
}
private String texte;
private long attente;
}
public class threed3{
public static void main(String args [])
{
ecrit e1=new ecrit ("bonjour",5);
ecrit e2=new ecrit ("bonsoir",10);
ecrit e3=new ecrit ("\n",35);
e1.start();
e2.start();
e3.start();
e1.interrupt();
System.out.println("arret du premier thread");
e2.interrupt();
e3.interrupt();
System.out.println("arret des autres threads");
}
}

Threads démons et arrêt brutal


Il existe deux catégories de threads: Threads utilisateurs et threads démons.

Caractéristique d'un démon :


Si à un moment donné, les seuls threads en cours d'exécution d'un même programme sont
des démons, ces derniers sont arrêtés brutalement et le programme se termine.
Par défaut un thread est créé dans la catégorie du thread qu'il a créé.
Pour faire d'un thread un démon, on effectue l'appel setDaemon(true) avant d'appeler
start().

Exemple : les trois threads sont maintenant des démons.


class ecrit extends Thread {
ecrit(String texte, long attente)
{
this.texte=texte;this.attente=attente;}
public void run()
{
try{
while (true)
{

6
if(interrupted()) return;
System.out.print(texte);
sleep(attente);
}
}
catch(InterruptedException e){return; }
}
private String texte;
private long attente;
}

class demons
{public static void main(String args [])
{
ecrit e1=new ecrit ("bonjour",50);
ecrit e2=new ecrit ("bonsoir",100);
ecrit e3=new ecrit ("\n",35);
e1.setDaemon(true);e1.start();
e2.setDaemon(true);e2.start();
e3.setDaemon(true);e3.start();
}
}

Synchronisation des threads


Les threads ont l'avantage d'appartenir à un même programme. Ils peuvent donc partager
les mêmes objets.
Des problèmes peuvent apparaitre : accès simultané à un même objet par deux threads,
blocage de l'un par l'autre, ...
Pour résoudre ces problèmes on utilise la synchronisation, …

 Méthodes synchronisées
L'environnement peut interrompre un thread à n'importe quelle instruction.

Exemple
Soit deux threads répétant indéfiniment les actions suivantes :
Thread 1 : Incrémentation d'un nombre et calcul de son carré
Thread 2 : affichage du nombre et de son carré.

Pb : le premier thread peut être interrompu entre l'incrémentation et le calcul du carré. Le


second thread affiche alors le nombre mais l'ancien carré.

Solution : Méthodes synchronisées.


Pour palier ce problème, java permet de déclarer des méthodes avec le mot clé synchronized.
À un instant donné, une seule méthode ainsi déclarée peut être appelée pour un objet donné.
Deux informations (N et carre) sont partagées entre deux threads.
Le premier thread fait les traitements et le second fait l'affichage.
Dans cet exemple la classe nombres dispose de deux méthodes mutuellement exclusives
synchronisée (synchronised) : calcul() et affiche().

7
Deux threads sont utilisés thrCalcul et thrAff. Les deux sont lancés par main et interrompus
lorsque l'utilisateur le veut.

 Exemple
class nombres
{public synchronized void calcul()
{
n++;
carre=n*n;
}

public synchronized void affiche()


{ System.out.println(n+" a pour carre "+carre); }

private int n=0;


private int carre;
}

class TrCalc extends Thread


{public TrCalc(nombres nomb)
{
this.nomb=nomb;
}
public void run()
{
try{
while(!interrupted())
{
nomb.calcul();
sleep(50);
}
}
catch(InterruptedException e){}
}
private nombres nomb;

class TrAff extends Thread


{public TrAff(nombres nomb)
{
this.nomb=nomb;
}
public void run()
{
try{
while(!interrupted())
{
nomb.affiche();
sleep(75);

8
}
}
catch(InterruptedException e){}
}
private nombres nomb;

class synchonisation1
{public static void main(String args [])
{
nombres nomb=new nombres();
Thread calc =new TrCalc(nomb);
Thread aff =new TrAff(nomb);System.out.println("carrees ..taper retour pour arreter ");
calc.start();
aff.start();

//String ligne=null;
int ligne;
clavier c=new clavier();
ligne=c.lireint();
//System.out.println("***********************");

calc.interrupt();
aff.interrupt();
}
}
Remarque
Si on élimine le mot synchronized des méthodes calcul et affiche il y aura perturbation entre
les deux threads.

 Notion de verrou
A un instant donné, une seule méthode synchronisée peut accéder à un objet donné.
Pour cela, pour chaque objet, il ya un verrou unique (clé) géré par l'environnement
permettant l'accès à l'objet.
Le verrou est attribué à la méthode synchronisée appelée pour l'objet et il est restitué à la
sortie de la méthode.
Tant que le verrou n'est pas restitué, aucune autre méthode synchronisée ne peut le
recevoir.
Les méthodes non synchronisées peuvent accéder à tout moment à l'objet

Exemple
Cosidérons la situation suivante :
synchronized void f(…) //f synchronisée
{
…. //partie 1
g(); //appel de la method g sur un même objet o
… //partie 2
}

9
Void g() //g n'est pas synchronisée
{
….
}
Si la méthode f est appelée sur un objet o, elle appelle à son tour la méthode g non
synchronisée.
Un thread peut interrompre la méthode g et modifie l'objet o entre les deux parties de la
méthode f.

L'instruction synchronized
L'utilisation d'une méthode synchronisée comporte deux contraintes :
L'objet concerné (celui sur lequel elle acquiert le verrou) est nécessairement celui qui l'a
appelée.
L'objet est verrouillé pour toute la durée de l'exécution de la méthode.

Pour acquérir un verrou sur un objet pour une durée limitée, on peut utiliser l'instruction
synchroized :

Synchronized (objet)
{
instructions
}
Exemple

 Interblocage
T1 un thread possédant le verrou sur l'objet o1 et il attend le verrou de l'objet o2.
T2 un thread possédant le verrou sur l'objet o2 et il attend le verrou de l'objet o1.
C'est à l'utilisateur de gérer cet interblocage (par ordonnancement par exemple).

Exemple

 Producteur et consommateur (Attente et notification)


Une méthode synchronisée peut appeler la méthode wait de l'objet dont elle possède le
verrou, ce qui a pour effet :
- De rendre le verrou à l'environnement qui pourra l'attribuer à une autre méthode
synchronisée.
- De mettre en attente le thread correspondant.

Une méthode synchronisée peut appeler la méthode notifyAll d'un objet pour prévenir tous les
threads en attente sur cet objet et leur donner la possibilité de s'exécuter.

Exemple
Considérons un programme qui gère une réserve. Il comporte :
- Deux threads qui ajoutent chacun une quantité donnée à cette reserve
- Un thread qui puise une quantité donnée.

- Règle : le thread ne peut puiser dans la réserve que si elle contient une quantité
suffisante.
- La classe Reserve dispose de deux méthodes synchronisées : puise et ajoute.

10
Lorsque la méthode puise s'aperçoit que la réserve est insuffisante, il appelle wait pour
mettre le thread correspondant en attente. Parallèlement la méthode ajoute appelle notifyAll
après chaque ajout.
Les trois threads sont lancés par main et interrompus lorsque l'utilisateur le veut.

class reserve extends Thread


{
public synchronized void puise (int v)throws InterruptedException
{
if (v<=stock)
{System.out.println(" on puise "+v);
stock-=v;
System.out.println(" et il reste "+stock);
}
else
{
System.out.println(" stock de "+stock +" insuffisant pour puiser "+v);
wait();
}
}

public synchronized void ajoute(int v)


{
stock+=v;
System.out.println(" on ajoute "+v +" et il y a maintenant "+stock);
notifyAll();
}
private int stock=500;
}

class TrAjout extends Thread


{public TrAjout(reserve r, int vol, int delai)
{
this.r=r; this.vol=vol; this.delai=delai;
}
public void run()
{
try{
while(!interrupted())
{
r.ajoute(vol);
sleep(delai);
}
}
catch(InterruptedException e){}
}
private int vol;
private reserve r;
private int delai;

11
}

class TrPuise extends Thread


{public TrPuise(reserve r, int vol, int delai)
{
this.r=r;this.vol=vol; this.delai=delai;
}
public void run()
{
try{
while(!interrupted())
{
r.puise(vol);
sleep(delai);
}
}
catch(InterruptedException e){}
}
private int vol;
private reserve r;
private int delai;
}

class synchonisation2
{public static void main(String args [])
{
reserve r=new reserve();
TrAjout ta1=new TrAjout(r,100,1500);
TrAjout ta2=new TrAjout(r,50,2000);
TrPuise tp=new TrPuise(r,300,1000);
System.out.println("suivi de stock -- taper entrer pour arreter ");
ta1.start();
ta2.start();
tp.start();

int ligne;
clavier c=new clavier();
ligne=c.lireint();

ta1.interrupt(); ta2.interrupt(); tp.interrupt();


}}

Utilisation de wait et notifyAll


Prenons encore l'exemple de calcul d'un nombre et de son carré. Les deux threads calcul et
affiche précédemment cités n'étaient pas coordonnés. On pouvait par exemple afficher
plusieurs fois avant d'incrémenter le nombre ou inversement. Pour les coordonner on doit
utiliser les méthodes wait et notifyAll ainsi qu'un indicateur boolean permettant aux deux
threads de communiquer entre eux.

12
Exemple
class nombres
{
public synchronized void calcul() throws InterruptedException
{
if(!pret)
{
n++;
carre=n*n;
pret=true;
notifyAll();
}
else wait();
}

public synchronized void affiche()


{
try {
if(pret)
{
System.out.println(n+" a pour carre "+carre);
notifyAll();
pret=false;
}
else wait();
}
catch (InterruptedException e){}
}
public boolean pret()
{return pret;
}
private int n=1;
private int carre;
private boolean pret=false;
}

class TrChange extends Thread


{public TrChange(nombres nomb)
{
this.nomb=nomb;
}
public void run()
{
try{
while(!interrupted()) {
nomb.calcul();
sleep(500);
}
}
catch(InterruptedException e){}

13
}
private nombres nomb;
}

class TrAff extends Thread


{public TrAff(nombres nomb)
{
this.nomb=nomb;
}
public void run()
{
try{
while(!interrupted())
{
nomb.affiche();
sleep(200);
nomb.affiche();
nomb.affiche();
}
}
catch(InterruptedException e){}
}
private nombres nomb;
}
class synchonisation3
{public static void main(String args [])
{
nombres nomb=new nombres();
Thread calc =new TrChange(nomb);
Thread aff =new TrAff(nomb);
System.out.println("carrees ..taper retour pour arreter ");
calc.start();
aff.start();
int ligne;
clavier c=new clavier();
ligne=c.lireint();
calc.interrupt(); aff.interrupt();
}}

Niveau de priorités et Threads


La priorité est entre 0 & 10. La valeur par défaut est 5.
static int MAX_PRIORITY : priorité Max qu'un thread peut avoir.
static int MIN_PRIORITY : priorité Min qu'un thread peut avoir.
static int NORM_PRIORITY : priorité par défaut affectée à un thread (la valeur de 5)
int getPriority(); retourne la priorité du thread
void setPriority(int p); modifie la priorité d'un thread.

ThreadGroup
C'est un ensemble de threads qui peut représenter des threads. Chaque thread donc appartient
à un ensemble. Le premier threadgroup d'un programme java est représenté par "main".

14
La méthode tostring affiche le nom du thread, sa priorité et son groupe.

Pour créer un thread d'un groupe donné de threads :


 On crée d'abord le groupe de threads:
ThreadGroup G = new ThreadGroup("GroupeMaitre");

 Puis on utilise un constructeur qui prend en argument un groupe de threads pour


créer le thread en question:
Thread t = new Thread(G) ;

Le groupe de thread permet par exemple de faciliter la gestion des threads.


Dans la classe Thread plusieurs méthodes traitent des groupes de threads:
static int activeCount(); : retourne le nombre de thread actives dans le groupe de threads
courants.
ThreadGroup getThreadGroup(); : retourne le thread groupe à qui ce thread appartient.

Etats d'un thread


Au départ on crée un objet thread. Tant qu'on ne fait rien il ne s'exécute pas.
L'appel de start() le rend disponible pour l'exécution. Il est considéré comme prêt.
L'environnement peut faire passer un thread de l'état prêt à l'état en cours d'exécution
(décision du système).
Le thread peut être interrompu ce qui le rend à l'état prêt (pour donner la main à un autre
thread).

1) Le thread est nouveau. L'objet est créé mais il n'y a pas encore de pile d'exécution.
Thread t = new Thread (r);

2) Le thread est exécutable (prêt). Quand vous lancez le thread, une pile est créée. Le
thread attend alors que la JVM le choisisse afin de s'exécuter. t. start ;

3) Le thread est en cours d'exécution. C'est le thread qui s'exécute, le seul. C'est
l'ordonnanceur de la JVM qui décide quel thread va s'exécuter à un moment donné.
Quand un thread s'exécute les autres sont en attente. A tout moment l'ordonnanceur
peut arrêter l'exécution du thread (il retourne alors dans l'état exécutable) et en choisir
un autre.

4) Le thread est bloqué. Le thread veut obtenir une ressource (une méthode d'un objet
verrouillé, attente du clavier, ...) non disponible pour le moment.

15
16

Vous aimerez peut-être aussi