Synchronisation de Threads
3.4.1 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.
Dans notre exemple, le tampon (buffer) sera présenté par une classe "Entier" qui a la capacité de stocker un seul entier (intPartage). La classe fournit deux méthodes pour lire (getIntPartage) et modifier (setIntPartage) le contenu de tampon.
Les deux classes producteur (ProduitEntier) et consomateur (ConsommeEntier) sont dérivées directement de la classe Thread. Le producteur produit 10 entiers et les met dans le tampon. Le consommateur les lit dans l'ordre et termine lorsqu'il lit le dernier nombre - 10.
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 { public class ConsommeEntier extends
Thread { public class CellulePartagee { |
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 |
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();
3.4.3 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 |
3.4.4 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. Exception 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(); } System.out.println("All ProduitEntier started by: "+ Thread.currentThread().getName()); for(int i=0;i<2;i++){ (new ConsommeEntier(h)).start(); } System.out.println("Thread "+Thread.currentThread().getName()+" finished"); } } |
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 |
Modifiez le 3.4.4 afin que le tampon ait la capacité de stocker plusieurs entiers (par exemple 3).