Concurrence et collaboration
Cette feuille en Postscript

1  Contrôle du point d'eau

On peut voir les threads comme de grands fauves (objet Lion) en compétition dans la jungle pour l'accès à un point d'eau (objet Mare). La mare est équipée d'un dispositif automatique qui enregistre chaque lion venant boire :
class Mare {
  
private int c ;
  
Mare() { c = 0 ; }
  
void boire() { c++ ; }
  
public String toString() { return Integer.toString(c) ; }
}
La classe Lion modélise les lions. Le comportement typique du lion est d'aller boire entre zéro et deux fois, puis d'aller se coucher (méthode run).
class Lion extends Thread {
  
static Mare mare = new Mare() ;

  
public void run() {
    
for (int i = (int)(Math.random() * 3) ; i > 0 ; i--)
      mare.boire() ;
  }

  
public static void main(String [] arg) {
    
int n = 64 ;
    
if (arg.length > 0) n = Integer.parseInt(arg[0]) ;
    
for (int i = 0 ; i < n ; i++) {
      
new Lion().run() ;
    }
    System.out.println("Bilan: " + mare) ;
  }
}
La méthode main procède au réveil des n lions, puis à la lecture du dispositif de la mare. Elle incarne le zoologue.
  1. Un peu de Java.
    1. Sans lire la doc, que renvoie Math.random() ?
    2. Quelle est l'interface utilisateur de ce programme ? Est elle améliorable ?
    3. Comment comprendre la construction new Lion().run() ?


  2. Notre programme utlise-t-il bien plusieurs threads ?

  3. On remplace new Lion().run(), par new Lion().start() et on suppose qu'il n'y a que deux lions buvant chacun une fois. Donner trois sénarios (de scheduling) conduisant à afficher respectivement 0, 1 et 2.

  4. Corriger le problème de la question précédente. On emploiera un tableau des lions et on utilisera la méthode join des objets de la classe Tread. L'appel t.join() retourne normalement quand l'exécution du thread t est terminée.

    En fait, si t est toujours actif, le thread qui invoque la méthode join est mis en attente et join retourne anormalement par l'exception InterruptedException quand ce thread en attente est interrompu par un troisième larron (qui appelle sa méthode interrupt).

    Proposer deux solutions, une qui échoue si le zoologue est interrompu, et une autre qui ignore les interruptions.

  5. Le dispositif de comptage de la mare souffre d'un défaut. Donner un scénario conduisant à afficher 1 en présence de deux lions allant boire une fois chacun.

  6. En présence du défaut, donner tous les résultats possibles dans les cas suivants :
    1. Un lion boit deux fois.
    2. Deux lions boivent deux fois chacun.
    3. Un lion boit une fois et un autre deux fois.


  7. Remédier au défaut.
Solution.

2  J'ai rendez-vous avec vous

Soit la classe RdV suivante :
class RdV {
  
boolean someone ;

  
RdV () { someone = false ; }

  
synchronized void sync() {
    
if (someone) {
      System.out.println() ;
      someone = 
false ;
      notify() ;
    } 
else {
      someone = 
true ;
      
try { wait() ; } catch (InterruptedException e) { System.exit(2) ; }
    }
  }
}
On notera que l'interuption du wait() se solde par un échec global de tout le programme et on en parlera plus.
  1. On suppose deux threads t0 et t1 et un objet r de la classe RdV fraîchement créé. Que se passe-t-il lorsque t0 appelle sync() de r, puis lorsque t1 appelle sync() de r.

  2. Qu'affiche le programme suivant :
    class Two extends Thread {
      
    static RdV rdv = new RdV () ;
      
    char id ;
      
    Two(char id) { this.id = id ; }

      
    public void run() {
        
    for (int i = 0 ; i < 10 ; i++) {
          System.out.print(id) ; rdv.sync() ;
        }
      }

      
    public static void main (String [] arg) {
        
    new Two('A').start() ;  new Two('B').start() ;
      }
    }


  3. On dit qu'un programme termine normalement quand tous les treads qu'il a lancés terminent, sur ma machine un programe qui ne termine pas normalement ne rend pas la main, il semble bloqué. On suppose que l'objet 'A' meurt inopinément (par exemple il déréférence bêtement null). Que se passe-t-il ?

  4. On suppose un troisième objet new Two('C').

  5. On cherche à genéraliser le rendez-vous à n threads. Un nouvel objet, la barrière, offre toujours une méthode sync(). Mais cette fois, un appel à sync se traduit par une attente où par un réveil collectif selon qu'il y a strictement moins de n-1 threads en attente ou n-1 (le rendez-vous est le cas particulier n=2). Voici un exemple d'utlisation.
    class All extends Thread {
      
    static Barrier rdv ;
      
    char id ;

      
    All(char id) { this.id = id ; }

      
    public void run() {
        
    for (int i = 0 ; i < 10 ; i++) {
          System.out.print(id) ;
          rdv.sync() ;
        }
      }

      
    public static void main (String [] arg) {
        
    int n = 26 ;
        rdv = 
    new Barrier(n) ;
        
    for (int i = 0 ; i < n ; i++)
          
    new All((char)('A' + i)).start() ;

      }
    }
    L'effet attendu est l'affichage de 10 lignes composées des 26 lettres de l'alphabet.

    Écrire la classe Barrier.

  6. Le comportement de notre barrière est assez statique, dans le sens que l'on doit savoir dès le départ que l'on souhaite synchroniser n threads. On veut le rendre plus dynamique à l'aide de deux méthodes, register() et checkout() destinées respectivement à annoncer que l'on souhaite participer aux rendez-vous et que l'on y renonce (évidemment on suppose une certaine discipline de la part des threads synchronisants).

    Écrire cette nouvelle barrière à partir de la précédente.

  7. Modifier la classe All pour utiliser la nouvelle barrière. Annoncer les affichages résultant des modifications.

  8. Blinder le code pour que la mort par exception d'un thread ne déclenche pas de blocage.
Solution.


Ce document a été traduit de LATEX par HEVEA et HACHA.