Se coordonner grâce aux méthodes wait et notify : cas oùles participants s'attendent l'un l'autre pour effectuer certaines tâches.                  

Se coordonner grâce aux méthodes wait et notify : cas où les participants s'attendent l'un l'autre pour effectuer certaines tâches.

     

Ce que fait le programme ci-dessous est très simple et ne vaut qu'à titre d'exemple.

Une méthode principale fait démarrer deux joueurs. Le jeu consiste à tirer au hasard un entier entre 0 et 9999. Celui des deux joueurs qui a tiré le plus grand nombre a gagné.

De façon à illustrer les méthodes wait et notify, lorsqu'un joueur a fini de jouer, il attend que l'autre ait fini, il se renseigne sur la valeur tirée par l'autre et conclut pour dire s'il a gagné ou perdu.

Chaque joueur synchronise son attente sur l'autre joueur comme le montre le morceau de code :

synchronized(adversaire)
  {
     while (!adversaire.tirageFait) adversaire.wait();
  }

Il est important de comprendre que c'est la thread qui est en cours d'exécution qui attend lorsqu'on invoque la méthode wait et non, ici par exemple, adversaire. On doit comprendre :
    adversaire.wait();
comme : adversaire fait attendre la thread qui est en cours d'exécution (comme pourrait le faire tout objet). C'est celui qui fait attendre la thread qui est en cours d'exécution qui pourra aussi réveiller celle-ci.
En conséquence, c'est à chaque joueur de réveiller l'autre en utilisant une synchronisation par rapport à lui-même, d'où le morceau de code :

synchronized(this)
  {
    notify();
  }
Il n'est pas utile que chaque joueur fasse une notification lorsqu'il donne la valeur true à leur attribut fini, car une notification est faite automatiquement quand un thread se termine.

A la fin de la méthode run de la classe Joueur, chaque joueur attend que l'autre joueur ait teminé pour dire au revoir.

import java.util.Random;

class Joueur extends Thread
{
  Random alea; 
  int resultat;
  String nom;
  boolean tiragefait = false;
  boolean fini = false;
  Joueur adversaire;
  
  Joueur(Random alea, String nom)
    {
      this.alea=alea;
      this.nom=nom;
    }

  public void run()
    {
      try
	  {
	     sleep(Math.abs(alea.nextInt())%1000);
	     resultat = Math.abs(alea.nextInt())%10000;
	     tiragefait = true;
	     synchronized(this)
	     {
	        notify();
	     }
	     synchronized(adversaire)
	     {
	        while (!adversaire.tiragefait) adversaire.wait();
	     }
	     String phrase = (resultat >= adversaire.resultat ?
                        ", j'ai gagné" : ", j'ai perdu");  
	     System.out.println(nom + " : " + resultat + phrase) ;
	     sleep(Math.abs(alea.nextInt()) % 1000);
	     fini = true;
	     synchronized(adversaire) 
             {
		while (!adversaire.fini) adversaire.wait();
	     }
         }
       catch(InterruptedException exc) {}
       System.out.println(nom + " vous dit au revoir");
    }
}

class Jeu
{
  public static void main(String[] argv)
    {
      Random alea = new Random();
     
      Joueur David = new Joueur(alea, "David");
      Joueur Antoine = new Joueur(alea, "Antoine");
      Antoine.adversaire = David;
      David.adversaire = Antoine;
      David.start();
      Antoine.start();
    }
}
On obtient par exemple :
Antoine : 2490, j'ai perdu
David : 4232, j'ai gagne
David vous dit au revoir
Antoine vous dit au revoir

Vous pouvez récupérer le programme d'exemple.

Lorsqu'un ensemble de threads se coordonnent dans un programme, il n'est pas facile de prouver que le programme fait dans tous les cas ce qu'on attend de lui et, en particulier, qu'il n'y a pas de risque d'interblocage ; on dit qu'il y a interblocage si, par exemple, un thread A attend quelque chose d'un thread B qui attend quelque chose d'un thread C qui attend quelque chose du thread A et qu'alors les threads A, B et C sont définitivement bloqués. Ici, il faut considérer le cas d'un des joueurs pour montrer qu'il ne peut pas être définitivement bloqué ; s'il attend lors du premier appel de la méthode wait, c'est que la variable tirageFait de son adversaire vaut false : celui-ci va donc ultérieurement, après avoir mis sa variable tirageFait à true, effectuer une notification ; il n'y a pas de blocage sur ce premier appel. Pour le deuxième appel à la méthode wait, après le passage de la variable fini à true, si le joueur attend, c'est que la variable fini de son adversaire vaut false ; celui-ci n'a donc pas encore atteint son passage dans le deuxième boucle while ; quand il le fera, il ne sera pas mis en attente, il terminera, ce qui provoquera une notification qui débloquera le joueur restant.


© Irène Charon, Télécom ParisTech 2011