12. Exclusion mutuelle

Introduction

Le mécanisme d'exclusion mutuelle présent dans Java est le moniteur. Si l'on désire définir une section critique, afin de s'assurer de la cohérence des données, nous devons utiliser le mot-clé synchronized. C'est de cette manière que l'on crée un moniteur.

Sécurisation d'une méthode

Lorsque l'on crée une instance d'une certaine classe, on crée également un moniteur qui lui est associé. Lorsque l'on applique le modificateur synchronized, on place la méthode (le bloc de code) dans ce moniteur, ce qui assure l'exclusion mutuelle.

Exemple

class mutexAcc {

    int accumulateur = 0;
    
    public synchronized void stocke(int val) {
        accumulateur += val;
    }
    
    public int lit() {
        return accumulateur;
    }

}
L'opération d'incrémentation += prend plusieurs instructions, et un changement de contexte entre elles pourrait créer une incohérence des résultats. Le modificateur synchronized indique que la méthode stocke fait partie du moniteur de l'instance, empêchant son exécution simultanée par plusieurs processus. Si le moniteur est déja occupé, les processus suivants seront mis en attente. L'ordre de réveil des processus n'est pas déterministe.

Sécurisation de blocs de code

L'utilisation de méthodes synchronisées trop longues peut créer une baisse d'efficacité. Avec Java, il est possible de placer n'importe quel bloc dans un moniteur, ce qui permet ainsi de réduire la longueur des sections critiques. Dans l'exemple suivant, le code de methode1 et de methode2 est équivalent.
synchronized void methode1() {
    // section critique...
}

void methode2() {
    synchronized(this) {
        // section critique...
    }
}

Exemple

public class Tortue {

    private Point pos;
    private int angle;
    
    public Tortue(int x,int y,int angle) {
        // ...
    }
    
    public synchronized void tourne(int degres) {
        angle += degres;
    }
    
    public synchronized void avance(int distance) {
        pos.x += (int) ((double)distance*Math.cos((double)angle));
        pos.y += (int) ((double)distance*Math.sin((double)angle));
    }

    public int angle() {
        return angle;
    }
    
    public synchronized Point pos() {
        return new Point(pos.x,pos.y);
    }

}

public class mutexLogo {

    public int lectureEntier() {
        // ...
        // lecture d'un nombre entier
        // pouvant prendre du temps
        // ...
    }

    public static void carre(Tortue tortue) {
        int largeur = lectureEntier();
        int longueur = lectureEntier();
        synchronized (tortue) {
            tortue.tourne(90);tortue.avance(largeur);
            tortue.tourne(90);tortue.avance(longueur);
            tortue.tourne(90);tortue.avance(largeur);
            tortue.tourne(90);tortue.avance(longueur);
        }
        // autres taches...
    }

}
Comme nous l'avons vu, quand le modificateur synchronized qualifie une méthode, on place cette méthode dans le moniteur de l'instance. Cela permet de sécuriser l'accès à une instance particulière d'un objet. En créant un bloc synchronized (tortue), on entre dans le moniteur associé à l'instance tortue. Dans l'exemple ci-dessus, si un processus est en train d'afficher un carré en faisant appel à mutexLogo.carre(tortue), un autre processus ne pourra pas déplacer la tortue. Malgré tout, pendant que l'on se trouve dans le méthode carre et que l'on est en train de lire les dimensions du carré, un autre processus pourra utiliser la tortue. En effet, le moniteur n'est pas occupé, parce que la méthode n'est pas synchronized, seul un bloc l'est.

Sécurisation des variable de classes

Considérons maintenant le cas oł il faut sécuriser l'accès à une variable de classe. La solution est simple, il faut créer un moniteur qui est commun à toutes les instances de la classe. La méthode getClass() retourne la classe de l'instance dans laquelle on l'appelle. Nous pouvons maintenant créer un moniteur qui utilise le résultat de getClass() comme "verrou".
class mutexStatic {

    private int accumulateur = 0;
    private static int acces = 0;

    public synchronized void stocke(int val) {
        accumulateur += val;
        synchronized (getClass()) {
            acces += 1;
        }
    }
    
    public int lit() {
        synchronized (getClass()) {
            acces += 1;
        }
        return accumulateur;
    }

    public int acces() {
        return acces;
    }
    
}
Cet exemple définit une classe d'accumulateur qui incrémente acces chaque fois que l'on accède à stocke ou à lit. La variable acces est une variable de classe (déclarée public), elle est donc partagée par les différentes instances de cette classe. La méthode getClass() retourne un objet de type Class avec lequel on crée un nouveau moniteur.
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:32 MET DST 1996