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 { 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 |
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(); } 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 |