Java permet de créer des applications multitâches via les threads. Un thread est un sous-processus pouvant s'exécuter en parallèle avec d'autres threads et pouvant utiliser des ressources communes. Nous utilisons intensivement les threads lors de l'exécution d'une classe : le Garbage Collector qui vient nettoyer la mémoire s'exécute par exemple au sein d'un thread séparé. Bien entendu, les threads ne s'exécutent pas réellement en parallèle à moins de disposer de plusieurs processeurs. L'illusion du parallélisme est donné par l'exécution fragmentée des programmes.
Il y a deux façons de créer un thread:
Dériver la classe de java.lang.Thread. Cette classe possédera une méthode run() qui contient le code principal du thread.
Implémenter l'interface Runnable. En effet, si la classe doit dériver d'une autre classe que Thread, elle peut contourner le problème via cet interface Runnable. Elle implementera l'unique méthode run().
Le thread sera lancé par la méthode start() appliquée à l'instance de Thread concernée.
Un thread se met en arrêt temporaire via la méthode statique sleep() de Thread. Attention, cette méthode est statique et ne peut être appelée de l'extérieur du thread pour le forcer au sommeil: elle s'appelle dans le thread lui-même. Le thread comportera alors un appel de type Thread.sleep([temps en ms]);
De la même façon, un thread peut se mettre en attente d'un événement par la méthode wait() qui doit être appelée dans la classe même. Dans ce cas, le thread attendra une notification de reprise d'activité pour reprendre. Un appel de notify() ( classe Object ) sur une instance de Thread permet de notifier à ce dernier de reprendre son exécution bloquée par un wait(). Un notifyAll() permet cette action sur tous les threads en attente.
Attention: Les méthodes
sleep() et wait() peuvent provoquer des
InterruptedException.
Des"verrous" peuvent être posés sur des blocs d'instruction ou des méthodes d'une classe afin d'éviter les états indéterminés en cas d'accès concurrents. Il faut poser le mot-clé synchronized sur une méthode ou un bloc d'instructions qui accède aux données suceptibles d'être modifiées par plusieurs threads en même temps. Un wait() ne peut par exemple se faire que dans un bloc synchronisé.
méthode |
action |
appliquée... |
---|---|---|
class
Thread méthode d'instance
|
Démarre la méthode run() du thread concerné.
|
De l'extérieur du Thread. |
class
Thread méthode de classe
|
Le thread passe en sommeil le temps spécifié en ms |
Dans le thread. |
class
Object méthode d'instance
|
Le thread courant passe en attente d'un notify |
Dans le thread. |
class
Object méthode d'instance |
Notifie à tous les threads en attente de reprendre leur exécution |
De l'extérieur du thread |
class
Object méthode d'instance
|
Notifie au thread sur lequel cette méthode est appliquée de reprendre son exécution. |
De l'extérieur du thread |
class
Thread méthode de classe
|
Force le thread courant à se met en pause et laisse la main au threads concurrents |
Dans le thread |
class
Thread méthode d'instance
|
Fixe la priorité du thread. Souvent les valeurs: Thread.MIN_PRIORITY, Thread.NORM_PRIORITY ou Thread.MAX_PRIORITY |
De l'extérieur |
class
Thread méthode d'instance
|
Teste si le thread est vivant |
De l'extérieur |
class
Thread méthode d'instance
|
Teste si le thread est dans un wait() |
De l'extérieur |
Les actions extérieures au thread ne sont pas forcement gérées par une classe séparée. En effet, l'interface Runnable donne le moyen simple à un thread de se lancer lui-même. Pour cela, il suffit au thread d'implementer Runnable puis de déclarer une variable d'instance de type Thread instancié de cette façon:
Thread tThread=new Thread(this);
En donnant un argument de type Runnable au constructeur de Thread puis en lançant tThread.start(), la méthode run() de notre thread sera exécutée à travers tThread. Ceci n'est vrai que si nous donnons un type Runnable au constructeur de tThread. Dans le cas contraire, ce sera la méthode run() de tThread qui sera exécutée lors d'un tThread.start() .
Dans cet exemple très simple, nous mettons en place une classe TestThread qui lance deux threads concurrents. Ces deux threads accèdent en concurrence à une ressource de la classe Ressource. L'un des threads incrémente la ressource et l'autre la décremente. Chaque thread va ensuite s'endormir pour une durée aléatoire.
ClasseTestThread
public
class TestThread implements Runnable{
boolean
bInc; / si vrai, le thread décremente la
ressource, sinon, il l'incrémente
Thread
tThread=new Thread(this);
Ressource
rRessource;
TestThread(Ressource
rRessource,boolean bInc){ /*le constructeur prend en argument la
ressource
partagée*/
this.rRessource=rRessource;
this.bInc=bInc;
tThread.start();//démarre
le thread dans lequel nous sommes et non tThread
}
public
void run(){
System.out.println("Thread
"+this +" started"); //affiche le no du
thread
while(true){
try{
if
(bInc){
rRessource.inc();
//incrémente la
ressource
Thread.sleep((int)(Math.random()*1000));
//dort jusqu'à 1
sec
}
else{
rRessource.dec();
//décremente la
ressource
Thread.sleep((int)(Math.random()*1000));
}
}
catch(InterruptedException
ie){
ie.printStackTrace();
}
}
}
public
static void main(String[] sArgs){
Ressource
rRessource=new Ressource(10);
TestThread
tt1=new TestThread(rRessource,true);
TestThread
tt2=new TestThread(rRessource,false);
}
}
ClasseRessource
class
Ressource{
int iComp; //la ressource elle-même
: un compteur
public Ressource(int iValue){
this.iComp=iValue;
}
public
synchronized void inc(){ //incremente la ressource de façon
synchronisée
iComp++;
System.out.println(iComp);
}
public
synchronized void dec(){ //décremente la ressource de
façon synchronisée
iComp--;
System.out.println(iComp);
}
}