13. Synchronisation : les méthodes wait() et notify()

Il peut être nécessaire de synchroniser des processus qui accèdent aux mêmes ressources. L'utilisation des moniteurs permet de garantir l'exclusion mutuelle, et pour la synchronisation, on utilisera des signaux, qui sont modélisés par les méthodes 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.

Exemple - tamponCirc.java

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;
    }

}

Exemple - utiliseTampon.java

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) {}
    }

}

Exécution

...
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.

Attention !

Le noyau Java ne fait aucune garantie concernant l'élection des processus lors d'un appel à 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).


Index général - Index concepts - Règles BNF
© 1996, DIP Genève, Alexandre Maret & Jacques Guyot
page générée Fri Jun 21 15:41:34 MET DST 1996