TP sur les threads

Page Home (contact) Retour TPs

Support de cours


Pour se chauffer...

Un "compteur" a un nom (Toto par exemple) et il compte de 1 à n (nombre entier positif quelconque). Il marque une pause aléatoire entre chaque nombre (de 0 à 5000 millisecondes par exemple).

Un compteur affiche chaque nombre (Toto affichera par exemple, "Toto : 3") et il affiche un message du type "*** Toto a fini de compter jusqu'à 10" quand il a fini.

Ecrivez la classe compteur et testez-la en lançant plusieurs compteurs qui comptent jusqu'à 10. Voyez celui qui a fini le plus vite.

Faites 2 versions : une où les threads sont créés avec une classe fille de Thread, et une où ils sont créés avec une instance d'une classe à part qui implémente Runnable.

Correction

Compteur.java (version avec classe fille de Thread)
CompteurRunnable.java (version avec Runnable à part)


Tris parallèles

Voici un algorithme de tri en ordre croissant d'une tranche de tableau comprise entre les éléments d'indices debut et fin :

trier(debut, fin) {
  si (fin - debut < 2) { // on a fini, pas d'appel récursif
    si (t[debut] > t[fin])
      echanger(t[debut], t[fin])
  }
  sinon {
    milieu = (debut + fin) / 2
    trier(debut, milieu)
    trier(milieu + 1, fin)
    triFusion(debut, fin) // tri fusion des 2 moitiés debut à milieu et milieu + 1 à fin
  }
}

On remarque que les 2 tris qui sont effectués avant la fusion sont indépendants l'un de l'autre et il est donc facile des les faire exécuter en parallèle par 2 threads.

Voici une version Java mono-tâche de cet algorithme.

  1. Mais, voici surtout une version Java multi-tâche de cet algorithme, qui utilise uniquement des join pour attendre la terminaison des 2 threads fils (qui eux-mêmes, de manière récursive, ont 2 fils dont ils attendent la terminaison pour pouvoir se terminer). Remarquez que la méthode main lance un thread de Tri, et qu'il ne faut pas non plus oublier d'attendre la fin de son exécution avant d'afficher le résultat final ! Pour réaliser cette synchronisation, on a aussi utilisé join. Testez cette version.
  2. En utilisant des wait-notify, codez une autre version multi-tâche de cet algorithme, en partant de ce squelette. Vous pouvez remarquer qu'il est important de choisir le bon objet (donc le bon "moniteur" associé) pour synchroniser les 2 threads fils et leur père.
    Pour ceux qui ont déjà eu un cours sur la concurrence : vous allez raisonner comme si vous aviez une variable de type condition. L'important est donc de déterminer la condition (booléenne) qui est attendue ; par contre, la variable de type condition (matérialisée par une file d'attente de thread) n'a pas à être créée explicitement. En effet elle existe au sein du moniteur implicitement associé à chaque objet (dès lors que cet objet contient du code synchronized).
    Relancez plusieurs fois (au moins une dizaine de fois) votre programme pour essayer de repérer des éventuels problèmes liés au multi-tâche.
  3. Écrivez ensuite une autre version qui utilise le framework "fork-join" introduit par le JDK 7.

Correction

Trieur.java

Trieur.java avec fork-join - TestTri.java


Tris parallèles avec la nouvelle API sur la concurrence

Une fois que vous aurez terminé cette version, vous pourrez étudier en détail celle-ci : elle fait appel à un outil de synchronisation du paquetage java.util.concurrent, un compteur de type CountDownLatch. Vous pouvez vous faire une idée rapide de ce paquetage en relisant l'introduction faite dans le cours ou en lisant ces transparents (version locale ici).

Ce code contient une "race condition" (situation de compétition en français) : lors de certaines exécutions, une NullPointerException est lancée. Si vous n'avez pas la "chance" de tomber sur un tel cas, ajoutez un "sleep(10)" (dormir un centième de seconde ; si 10 ne suffit pas, augmenter ce nombre) dans le constructeur public de TrieurCompteur, juste après le this(...) pour favoriser l'arrivée d'un tel cas. Corrigez le code pour ne plus avoir ce problème.

Modifiez la version utilisant le compteur de type CountDownLatch afin d'utiliser un sémaphore pour la synchronisation entre 2 threads fils et leur père.

Correction

TrieurCompteur sans problème de race condition
TrieurSemaphore.java


Pour ceux qui ont déjà fini....

Vous ne pourriez pas encapsuler ce join() dans la version "avec join" de l'exercice "Tris parallèles" pour que l'utilisateur de la classe Trieur ne risque pas d'oublier de le faire ? Par la même occasion, encapsulez le start() pour que l'utilisateur ne sache pas si le tri est effectué en parallèle ou non.

Produit de 2 matrices en multi-tâches

Rappel : (Aij) . (Bkl) = (Pmn) avec Pmn = Σ (Amj . Bjn)

On remarque que les calculs des Pmn sont indépendants les uns des autres. On peut donc facilement effectuer ces calculs en parallèle.
Écrivez une classe Matrice (n'implantez que les méthodes utiles pour effectuer le produit de 2 matrices en parallèle). Testez dans une méthode main.


Retour TPs