Synchronisation de Threads

Partage des ressources sans synchronisation

Dans une relation producteur - consommateur, un thread producteur appelle une méthode de production qui dépose un séquence de nombres (utilisons 1, 2, 3, ...) dans une zone de mémoire partagée. Le thread consommateur lit ces données:

public class Entier {
    private int intPartage = -1;
   public void setIntPartage( int val ){
      System.out.println( Thread.currentThread().getName() +
                 " met intPartage а " + val );
      intPartage = val;
   }
    public int getIntPartage(){
       System.out.println( Thread.currentThread().getName() +
                 " recupere intPartage " + intPartage );
       return intPartage;
   }
}

public class ProduitEntier extends Thread {
   private Entier pGarde;
   public ProduitEntier( Entier h ){
       super( "ProduitEntier" );
       pGarde = h;
   }
   public void run(){
      for ( int compteur = 1; compteur <= 10; compteur++ ) {
          // dormir pour une duree aleatoire.
          try {Thread.sleep( (int) ( Math.random() * 3000 ) );}
          catch( InterruptedException e ) {
              System.err.println( e.toString() );
          }
          pGarde.setIntPartage( compteur );
      }
      System.out.println( getName() + " a termine"  );
   }
}

public class ConsommeEntier extends Thread {
   private Entier cGarde;
   public ConsommeEntier( Entier h ){
      super( "ConsommeEntier" );
      cGarde = h;
   }
   public void run(){
      int val, somme = 0;
      do {
         // dormir pour une duree aleatoire.
         try {Thread.sleep( (int) ( Math.random() * 1000 ) );}
         catch( InterruptedException e ) {
              System.err.println( e.toString() );
         }
         val = cGarde.getIntPartage();
         somme += val;
       } while ( val != 10 );
       System.out.println(
           getName() + " a lu des valeurs dont le total vaut: " + somme);
   }
}

public class CellulePartagee {
   public static void main( String args[] ){
      Entier h =new Entier();
      ProduitEntier p = new ProduitEntier( h );
      ConsommeEntier c = new ConsommeEntier( h );
      p.start();
      c.start();
   }
}

ConsommeEntier recupere intPartage -1
ConsommeEntier recupere intPartage -1
ProduitEntier met intPartage а 1
ConsommeEntier recupere intPartage 1
ProduitEntier met intPartage а 2
ConsommeEntier recupere intPartage 2
ConsommeEntier recupere intPartage 2
ConsommeEntier recupere intPartage 2
ProduitEntier met intPartage а 3
ConsommeEntier recupere intPartage 3
ConsommeEntier recupere intPartage 3
ConsommeEntier recupere intPartage 3
ConsommeEntier recupere intPartage 3
ConsommeEntier recupere intPartage 3
ProduitEntier met intPartage а 4
ConsommeEntier recupere intPartage 4
ConsommeEntier recupere intPartage 4
ConsommeEntier recupere intPartage 4
ConsommeEntier recupere intPartage 4
ConsommeEntier recupere intPartage 4
ProduitEntier met intPartage а 5
ConsommeEntier recupere intPartage 5
ConsommeEntier recupere intPartage 5
ConsommeEntier recupere intPartage 5
ProduitEntier met intPartage а 6
ConsommeEntier recupere intPartage 6
ConsommeEntier recupere intPartage 6
ConsommeEntier recupere intPartage 6
ConsommeEntier recupere intPartage 6
ConsommeEntier recupere intPartage 6
ConsommeEntier recupere intPartage 6
ProduitEntier met intPartage а 7
ConsommeEntier recupere intPartage 7
ConsommeEntier recupere intPartage 7
ConsommeEntier recupere intPartage 7
ConsommeEntier recupere intPartage 7
ProduitEntier met intPartage а 8
ConsommeEntier recupere intPartage 8
ConsommeEntier recupere intPartage 8
ProduitEntier met intPartage а 9
ConsommeEntier recupere intPartage 9
ConsommeEntier recupere intPartage 9
ConsommeEntier recupere intPartage 9
ConsommeEntier recupere intPartage 9
ConsommeEntier recupere intPartage 9
ConsommeEntier recupere intPartage 9
ProduitEntier met intPartage а 10
ProduitEntier a termine
ConsommeEntier recupere intPartage 10
ConsommeEntier a lu des valeurs dont le total vaut: 199
 

Synchronisation

Java utilise ce que l'on appelle des "moniteurs" pour assurer la synchronisation. Le moniteur représente un objet qui permet a un seul thread à la fois d'exécuter une méthode ou un bloc de code. Ceci est accomplit en verrouillant l'objet lors de l'invocation de la méthode ou du bloc de code.

On utilise le mot clé synchronized pour marqué qu'une méthode

public synchronized int methode1{
      ...
}

ou bien un bloc de code

synchronized(objet)
    while(...) {
           ...
    }

devra verrouillé l'objet quand un thread entre dans la méthode ou bien dans le bloc. On dit aussi que l'ont obtient le verrouillage. S'il y a plusieurs méthode  synchronized , un seul de ces méthode est active à la fois sur un objet, tous les autres objets qui tentent d'invoquer des méthodes synchronized  doivent attendre. Quand la méthode synchronized  termine son exécution, le verrou sur l'objet est libéré et le moniteur laisse le thread prêt de la plus haute priorité tenter d'invoquer  l'exécution d'une méthode synchronized.

Si un thread qui s'exécute dans une méthode synchronized  détermine, qu'il ne peut poursuivre, alors il appel wait() va dans l'état "blocked" et libère le verrouillage de l'objet. Pour sortir de cette état elle devra recevoir notyfy() d'un autre thread qui sorte d'une méthode synchronized.

notifyAll();

Producteur - consommateur avec synchronisation:

public class Entier {
    private int intPartage = -1;
    private boolean inscriptible= true;
   public synchronized void setIntPartage( int val ){
      while(!inscriptible) {
          try{     wait();   }
          catch(InterruptedException e){
              System.err.println(e);
          }
      }
      System.out.println( Thread.currentThread().getName() +
                 " met intPartage а " + val );
      intPartage = val;
      inscriptible = false;
      notify();
   }
    public synchronized int getIntPartage(){
      while(inscriptible) {
          try{     wait();   }
          catch(InterruptedException e){
              System.err.println(e);
          }
      }
       System.out.println( Thread.currentThread().getName() +
                 " recupere intPartage " + intPartage );
       inscriptible = true;
       notify();
       return intPartage;
   }
}
ProduitEntier met intPartage а 1
ConsommeEntier recupere intPartage 1
ProduitEntier met intPartage а 2
ConsommeEntier recupere intPartage 2
ProduitEntier met intPartage а 3
ConsommeEntier recupere intPartage 3
ProduitEntier met intPartage а 4
ConsommeEntier recupere intPartage 4
ProduitEntier met intPartage а 5
ConsommeEntier recupere intPartage 5
ProduitEntier met intPartage а 6
ConsommeEntier recupere intPartage 6
ProduitEntier met intPartage а 7
ConsommeEntier recupere intPartage 7
ProduitEntier met intPartage а 8
ConsommeEntier recupere intPartage 8
ProduitEntier met intPartage а 9
ConsommeEntier recupere intPartage 9
ProduitEntier met intPartage а 10
ProduitEntier a termine
ConsommeEntier recupere intPartage 10
ConsommeEntier a lu des valeurs dont le total vaut: 55
 

 

 Plusieurs producteurs et consommateurs

Un consommateur doit terminer quand tous les producteurs ont terminé et il n'y a aucune valeur produit - compteur des producteurs actifs. Ecxeption pour terminer. "Timed waiting" pour ne pas rater la terminaision du dernier producteur.

public class Entier {
    private int intPartage = -1;
    private int nomP =0;
    private boolean inscriptible= true;
    public synchronized void incP(){        nomP++;    }
    public synchronized void decP(){        nomP--;    }
   public synchronized void setIntPartage( int val ){
      while(!inscriptible) {
          System.out.println("\t"+Thread.currentThread().getName()+" waiting");
          try{     wait();   }
          catch(InterruptedException e){
              System.err.println(e);
          }
      }
      System.out.println( Thread.currentThread().getName() +
                 " met intPartage a " + val );
      intPartage = val;
      inscriptible = false;
      notifyAll();
   }
    public synchronized int getIntPartage() throws NoP{
      while(inscriptible) {
          if(nomP==0)  throw new NoP()  ;
          System.out.println("\t"+Thread.currentThread().getName()+" waiting");
          try{     wait(1000);  }
          catch(InterruptedException e){
              System.err.println(e);
          }
      }
       System.out.println( Thread.currentThread().getName() +
                 " recupere intPartage " + intPartage );
       inscriptible = true;
       notifyAll();
       return intPartage;
   }
}
-----------------------------------------------------------------------------
public class ProduitEntier extends Thread {
   private Entier pGarde;
   public ProduitEntier( Entier h ){
       super( "ProduitEntier " +(int)(Math.random()*1000));
       pGarde = h;
   }
   public void run(){
       pGarde.incP();
       System.out.println( getName() + " starting"  );
      for ( int compteur = 1; compteur <= 4; compteur++ ) {
          // dormir pour une duree aleatoire.
          try {Thread.sleep( (int) ( Math.random() * 3000 ) );}
          catch( InterruptedException e ) { System.err.println( e.toString() );   }
          pGarde.setIntPartage( compteur );
      }
      pGarde.decP();
      System.out.println( getName() + " a termine"  );   
   }
}
-----------------------------------------------------------------------------
public class ConsommeEntier extends Thread {
    private Entier cGarde;
    public ConsommeEntier( Entier h ){
        super( "ConsommeEntier "+(int)(Math.random()*1000) );
        cGarde = h;
    }
    public void run(){
        System.out.println( getName() + " starting"  );
        int val;
        do {
            try {Thread.sleep( (int) ( Math.random() * 1000 ) );}
            catch( InterruptedException e ) {
                System.err.println( e.toString() );
            }
            try{
                val = cGarde.getIntPartage();
            }catch (NoP exc){
                break;
            }
            System.out.println("\t\t"+this.getName()+" a lu la valeur "+val);
        } while ( true );
        System.out.println( getName() + " a fini");
    }
}
----------------------------------------------------------------------------
public class NoP extends Exception{
    NoP(){
        System.out.println("Exception NoP jete");
    }
  
}
---------------------------------------------------------------------------
public class CellulePartagee {
    public static void main( String args[] ){
        Entier h =new Entier();
        for(int i=0;i<3;i++){
            (new ProduitEntier( h )).start();
        }
        for(int i=0;i<2;i++){
            (new ConsommeEntier(h)).start();
        }
    }
}
ProduitEntier 828 starting
ProduitEntier 836 starting
ProduitEntier 707 starting
ConsommeEntier 191 starting
ConsommeEntier 114 starting
    ConsommeEntier 191 waiting
ProduitEntier 828 met intPartage a 1
ConsommeEntier 191 recupere intPartage 1
        ConsommeEntier 191 a lu la valeur 1
    ConsommeEntier 114 waiting
    ConsommeEntier 191 waiting
ProduitEntier 707 met intPartage a 1
ConsommeEntier 191 recupere intPartage 1
        ConsommeEntier 191 a lu la valeur 1
    ConsommeEntier 114 waiting
    ConsommeEntier 191 waiting
ProduitEntier 828 met intPartage a 2
ConsommeEntier 191 recupere intPartage 2
        ConsommeEntier 191 a lu la valeur 2
    ConsommeEntier 114 waiting
ProduitEntier 836 met intPartage a 1
ConsommeEntier 114 recupere intPartage 1
        ConsommeEntier 114 a lu la valeur 1
    ConsommeEntier 191 waiting
ProduitEntier 828 met intPartage a 3
ConsommeEntier 191 recupere intPartage 3
        ConsommeEntier 191 a lu la valeur 3
    ConsommeEntier 114 waiting
    ConsommeEntier 191 waiting
ProduitEntier 836 met intPartage a 2
ConsommeEntier 191 recupere intPartage 2
        ConsommeEntier 191 a lu la valeur 2
    ConsommeEntier 114 waiting
ProduitEntier 707 met intPartage a 2
ConsommeEntier 114 recupere intPartage 2
        ConsommeEntier 114 a lu la valeur 2
ProduitEntier 836 met intPartage a 3
    ProduitEntier 836 waiting
ConsommeEntier 114 recupere intPartage 3
ProduitEntier 836 met intPartage a 4
        ConsommeEntier 114 a lu la valeur 3
ProduitEntier 836 a termine
ConsommeEntier 191 recupere intPartage 4
        ConsommeEntier 191 a lu la valeur 4
ProduitEntier 828 met intPartage a 4
ProduitEntier 828 a termine
ConsommeEntier 191 recupere intPartage 4
        ConsommeEntier 191 a lu la valeur 4
ProduitEntier 707 met intPartage a 3
ConsommeEntier 114 recupere intPartage 3
        ConsommeEntier 114 a lu la valeur 3
    ConsommeEntier 191 waiting
    ConsommeEntier 114 waiting
    ConsommeEntier 191 waiting
ProduitEntier 707 met intPartage a 4
ProduitEntier 707 a termine
ConsommeEntier 191 recupere intPartage 4
        ConsommeEntier 191 a lu la valeur 4
Exception NoP jete
ConsommeEntier 114 a fini
Exception NoP jete
ConsommeEntier 191 a fini