:: Enseignements :: Master :: M1 :: 2019-2020 :: Java Avancé ::
[LOGO]

TP noté de Concurrence


Le but de ce TP noté est de vérifier que vous maîtrisez les mécanismes de base de protection de code exécuté de manière concurrente. Il y a aussi un peu de lambda :)

Vos sources Java produites pendant le TP devront être placées sous le répertoire EXAM de votre compte ($HOME) (qui est vierge dans l'environnement de TP noté); sinon, elles ne seront pas récupérées.

Tout document papier est proscrit. Vous pouvez consulter la javadoc dans Eclipse (onglet Javadoc) ou en lançant la commande jdoc dans un shell. Les seuls documents électroniques autorisés sont les supports de cours à l'url http://igm.univ-mlv.fr/~forax/ens/java-avance/cours/pdf/.

La javadoc est accessible en local : /usr/local/apps/java13/docs/api/index.html.

Exercice 1 - Election

On cherche à écrire une classe Election thread safe qui permet d'élire une thread chef parmi plusieurs threads utilisant la même instance de la classe Election.
La classe Election possède une unique méthode publique iWantToBeTheGloriousLeader qui ne prend rien en paramètre et renvoie l'objet Thread qui en considérer comme la thread chef.
L'implantation est extrêmement simple, la première thread qui appelle iWantToBeTheGloriousLeader est considéré comme la thread chef.

Le code de la classe Election est le suivant:
public class Election {
  ...
  public Thread iWantToBeTheGloriousLeader() {
    //  TODO
    return null;
  }
  
  public static void main(String[] args) throws InterruptedException {
    var numberOfThreads = 4;
    var sleepTime = 1_000;
    
    // TODO
    ...
    System.out.println("end !");
  }
}
   

  1. Écrire le main qui créé autant de threads que numberOfThreads, démarre celles-ci, attend que toutes les threads aient fini, puis affiche "end !".
    Chaque thread va dans un premier temps attendre sleepTime millisecondes puis appeler la méthode iWantToBeTheGloriousLeader et enfin afficher son nom (le résultat de getName sur l'objet Thread) ainsi que le nom du chef (la valeur de retour de iWantToBeTheGloriousLeader).
    Voici un exemple d'affichage
    current: Thread-0 leader: Thread-0
    current: Thread-3 leader: Thread-0
    current: Thread-1 leader: Thread-0
    current: Thread-2 leader: Thread-0
    end !
        
  2. Écrire la méthode iWantToBeTheGloriousLeader de la classe Election pour que celle-ci soit thread-safe en utilisant pour cela le mot clé synchronized.
  3. On souhaite avoir une nouvelle version de Election nommée LockFreeElection, elle aussi thread-safe, qui utilise les classes du package java.util.concurrent.atomic pour implanter la méthode iWantToBeTheGloriousLeader de façon lock-free.
  4. Enfin, on souhaite avoir une troisième version nommée LockFreeElection2, toujours thread-safe, qui utilise la classe VarHandle pour implanter la méthode iWantToBeTheGloriousLeader de façon lock-free.

Exercice 2 - Aggregator

On souhaite implanter une classe thread-safe Aggregator qui agrège des données provenant de plusieurs threads (au moins une) suivant un algorithme (une opération) donné à la construction (somme, minimum, maximum, etc...). On fait patienter les threads tant qu'elles n'ont pas toutes fourni leur données, puis on renvoie à toutes les threads le résultat de l’agrégation, c'est à dire de l'application de l'algorithme sur les données.

Remarque : L'opération donnée à la construction est binaire. Si on note # cette opération et que les threads ont fourni les valeurs x1, x2, x3, ..., xn, le calcul attendu est (((x1 # x2) # x3 ) ... # xn). Pour simplifier, on supposera que l'opération est commutative : a # b = b # a et associative : (a # b) # c = a # (b # c).

Prenons, par exemple, une classe Aggregator construite en indiquant qu'il y a 3 threads et que l'opération est Integer::sum. Supposons que les threads appellent respectivement la méthode int aggregate(int) avec les valeurs 0, 1 et 2. Alors chaque appel à la méthode aggregate va renvoyer la valeur 3 (0 + 1) + 2.

Le code de la classe Aggregator est le suivant:
public class Aggregator {
  ...
 
  // constructeur
  
  public int aggregate(int value) {
    // TODO
    return 0;
  }
  
  public static void main(String[] args) throws InterruptedException {
    var numberOfThreads = 5;
    var aggregator = new Aggregator(numberOfThreads, Integer::sum);
    
    // TODO
  }
}
   

  1. Compléter le code de la méthode main pour créer numberOfThreads threads et démarrer celles-ci. Chaque thread va appeler la méthode aggregate avec son numéro (de 0 à numberOfThreads - 1) et afficher ce numéro ainsi que la valeur de retour de aggregate.
    Voici un exemple d'affichage avec 5 threads (pour l'instant, aggregate renvoie 0):
    thread 3 0
    thread 1 0
    thread 4 0
    thread 2 0
    thread 0 0
        
  2. Écrire le code de la classe Aggregator et sa méthode aggregate telle que :
    • Les valeurs passées en paramètre sont agrégées en utilisant l'opération spécifiée à la construction.
    • Une fois que toutes les threads ont soumis leur valeur, la méthode renvoie la valeur agrégée à toutes les threads ayant appelées aggregate.
    • On suppose que la méthode aggregate ne peut pas être appelée par plus de threads que prévues à la construction.

    L'implantation doit utiliser les verrous ré-entrants du package java.util.concurrent.locks.
    Remarque : pour que l’agrégation soit possible, il doit y avoir au moins une valeur à agréger.
    On obtient désormais un affichage qui ressemble à cela.
    thread 3 10
    thread 1 10
    thread 4 10
    thread 2 10
    thread 0 10
        
  3. On souhaite écrire une nouvelle version dans une classe nommée Aggregator2 telle que, si une des threads est interrompue dans aggregate, alors elle lève l'exception IllegalStateException et toutes les autres threads participantes lèvent aussi l'exception IllegalStateException.
    Pour tester : Modifier le main pour créer 4 threads ainsi qu'une instance de Aggregator2 qui attend 5 threads. Puis, à la fin du main, interrompre la thread 0.
    Votre affichage devrait être
    thread 1 java.lang.IllegalStateException: broken
    thread 0 java.lang.IllegalStateException: interrupted
    thread 3 java.lang.IllegalStateException: broken
    thread 2 java.lang.IllegalStateException: broken
        
    afin de constater que la thread 0 est bien interrompue et que cela entraîne l'arrêt des autres threads.