wait()
et notify()
.
Un des exemples classiques de l'utilisation des signaux est celui des producteurs-consommateurs. Prenons un tampon borné de n objets, un processus producteur et un processus consommateur.
class tamponCirc {
private Object tampon[];
private int taille;
private int en, hors, nMess;
/** constructeur. crée un tampon de taille éléments */
public tamponCirc (int taille) {
tampon = new Object[taille];
this.taille = taille;
en = 0;
hors = 0;
nMess = 0;
}
public synchronized void depose(Object obj) {
while (nMess == taille) { // si plein
try {
wait(); // attends non-plein
} catch (InterruptedException e) {}
}
tampon[en] = obj;
nMess++;
en = (en + 1) % taille;
notify(); // envoie un signal non-vide
}
public synchronized Object preleve() {
while (nMess == 0) { // si vide
try {
wait(); // attends non-vide
} catch (InterruptedException e) {}
}
Object obj = tampon[hors];
tampon[hors] = null; // supprime la ref a l'objet
nMess--;
hors = (hors + 1) % taille;
notify(); // envoie un signal non-plein
return obj;
}
}
class producteur extends Thread {
private tamponCirc tampon;
private int val = 0;
public producteur(tamponCirc tampon) {
this.tampon = tampon;
}
public void run() {
while (true) {
System.out.println("je depose "+val);
tampon.depose(new Integer(val++));
try {
Thread.sleep((int)(Math.random()*100)); // attend jusqu'a 100 ms
} catch (InterruptedException e) {}
}
}
}
class consommateur extends Thread {
private tamponCirc tampon;
public consommateur(tamponCirc tampon) {
this.tampon = tampon;
}
public void run() {
while (true) {
System.out.println("je preleve "+((Integer)tampon.preleve()).toString());
try {
Thread.sleep((int)(Math.random()*200)); // attends jusqu'a 200 ms
} catch (InterruptedException e) {}
}
}
}
class utiliseTampon {
public static void main(String args[]) {
tamponCirc tampon = new tamponCirc(5);
producteur prod = new producteur(tampon);
consommateur cons = new consommateur(tampon);
prod.start();
cons.start();
try {
Thread.sleep(30000); // s'execute pendant 30 secondes
} catch (InterruptedException e) {}
}
}
...
je depose 165
je depose 166
je preleve 161
je depose 167
je preleve 162
je depose 168
je preleve 163
je depose 169
je preleve 164
je depose 170
je preleve 165
je depose 171
je preleve 166
je preleve 167
...
Nous déclarons 2 processus, un producteur
et un consommateur
. Les
données sont produites plus vite que le consommateur ne peux les prélever, à cause de
la différence de durée des délais (aléatoires) introduits dans les 2 processus.
Il existe deux variantes de la méthode wait()
qui permettent de
spécifier un temps limite après lequel le processus sera réveillé, sans avoir
à être notifié par un processus concurrent. Il s'agit de wait(long milli)
,
qui attend milli millisecondes, et de wait(long milli,int nano)
qui attend nano nanosecondes en plus du temps milli. Il n'est pas possible
de savoir si wait()
s'est terminé à cause d'un appel à notify()
par un autre processus, ou de l'épuisement du temps.
La méthode notifyAll()
réveille tous les processus, et dès que le moniteur
sera libre, ils se réveilleront tour à tour.
notify()
. En particulier, il ne garantit pas que les processus seront débloqués
dans l'ordre ou ils ont été bloqués. C'est à cause de cela que l'on a placé wait()
dans une boucle dans l'exemple précédent, car un consommateur pourrait réveiller un autre
consommateur alors que le tampon est vide.
Remarquons aussi que les méthodes wait
, notify()
et
notifyAll()
ne peuvent être appelées que dans des méthodes
synchronisées (synchronized
).