La synchronisation possède un mécanisme de notification de threads. Ce mécanisme permet de faire communiquer différents threads entre eux. Ce genre de communication repose sur un système d'attente et de notification.

La classe Object définit, à cet effet, trois méthodes wait(), notify() et notifyAll(). Tous les objets d'un programme Java, héritant directement ou indirectement de la classe Object, peuvent tous utiliser les méthodes précitées.

Les threads exécutant des opérations sur un objet partagé, peuvent avoir besoin de dialoguer entre eux, afin d'indiquer par exemple qu'un traitement spécifique (ex. : lecture sur un socket) est terminé et donc qu'un autre (ex.: mise en forme d'un résultat) peut prendre le relai. Dans ce système, un thread attend donc de recevoir une notification d'un thread concurrent qui devra avoir libéré le verrou d'objet, pour qu'il puisse constater qu'une condition soit satisfaite afin d'exécuter sa tâche. S'il ne reçoit pas de notification, il reste en attente ou si la condition n'est pas bonne, il se rendort.

Les méthodes wait(), notify() et notifyAll() ne peuvent être utilisées que depuis des méthodes ou des blocs de code synchronisés. Dans le cas contraire, une exception IllegalMonitorStateException sera immanquablement levée si ces méthodes sont invoquées par du code qui ne possède pas le moniteur sur l'objet courant.

public synchronized void methode(){
    //...
    this.wait();
    //...
}

synchronized(this){
    //...
    this.notifyAll();
    //...
}

La méthode wait() correspond à une mise en attente du thread qui l'invoque, jusqu'à la réception d'une notification. Cette mise en attente s'accompagne d'une libération de tous les moniteurs que ce thread aurait placés sur des objets communs. Ainsi, les verrous mutex de ces objets deviennent disponibles pour d'autres threads concurrents.

Deux autres méthodes wait(), prenant des valeurs temporelles en millisecondes ou/et en nanosecondes en arguments, permettent de mettre en attente un thread jusqu'à ce qu'une période de temps déterminée se soit écoulée.

La méthode notify() ou notifyAll() indique respectivement à un thread ou à tous les threads précédemment mis en attente par la méthode wait(), de se réveiller. Dans la plupart des cas, la méthode notifyAll() est utilisée car si plusieurs threads synchronisés sur un objet ont invoqué la méthode wait(), alors il est nécessaire de leur envoyer une notification afin que chacun puisse vérifier leur condition de reprise. Dans le cas contraire, un seul thread est réveillé et celui-ci peut voir sa condition de redémarrage non satisfaite.

Ce mécanisme de communication peut se substituer à une boucle de scrutation avec une bien meilleure efficacité en terme de commodité et de performance. En effet, l'invocation de la méthode sleep() retarde du temps qui lui est imposé, la vérification d'une condition. L'utilisation de la méthode wait() évite ce temps de latence, puisque le thread ne se réveillera que lorsqu'il sera notifié par un autre thread qui aura vérifié la condition.

public class Exemple {
  private int a;
  public synchronized void scruter(){
    while(a >= 10){
      try {
        //Traitement...
        Thread.sleep(100);
      }
      catch(InterruptedException e){
        e.printStackTrace();
      }
    }
  }
  public synchronized void remettreAZero(){
    if(a > 0) a = 0;
  }
}

public class Exemple {
private int a;
  public synchronized void scruter(){
    while(a > 0){
      try {
        //Traitement conditionné...
        this.wait();
      }
      catch(InterruptedException e){
      }
    }
  }
  public synchronized void remettreAZero(){
    if(a > 0){
      a = 0;
      this.notify();
    }
  }
}

Dans un programme plus complexe, la boucle contenant l'appel de la méthode wait() doit être conservée car au possible réveil des threads concurrents par l'une des méthodes de notification, la condition attendue peut ne pas être satisfaite. Si le thread en attente se réveille, il ne testera pas la condition et sortira de la méthode, engendrant des risques pernicieux pour le déroulement du programme.

public synchronized void scruter(){
  if(condition == false){
    try {
      //Traitement conditionné...
      this.wait();
    }
    catch(InterruptedException e){
    }
  }
}

La mise en attente temporisée par deux des méthodes wait() s'avère particulièrement utile dans les cas où il est nécessaire d'arrêter prématurément le sommeil d'un thread et évidemment avant qu'il ne reçoive une notification d'un autre thread. Ces méthodes procure un avantage décisif. Si le thread chargé de notifier, ne peut être en mesure d'accomplir son action, le réveil programmé comblera cette lacune.

public synchronized void effectuerTraitement(){
  while(condition == false){
    try {
      //Traitement conditionné...
      //Au bout de 12 secondes, le thread se réveille
      this.wait(12000);
    }
    catch(InterruptedException e){
    }
  }
}

Les méthodes de classe ne peuvent utiliser directement ce mécanisme de notification et de mise en attente. Toutefois, il est possible d'utiliser un objet statique lié par une relation un à un à la classe courante, à partir duquel les méthodes wait, notify() et notifyAll() pourront être appelées. Ce genre d'objet peut être le verrou de classe.

public class UneClasse {
  private static Object verrou = new Object();

  public static void scruter(){
    synchronized(verrou){
      while(a > 0){
        try {
          //Traitement conditionné...
          verrou.wait();
        }
        catch(InterruptedException e){
        }
      }
    }
  }
  public static void avertir(){
    synchronized(verrou){
      if(a > 0){
        a = 0;
        verrou.notify();
      }
    }
  }
}
Sommaire