Examen de 2 h sur machine


L'examen est composé d'une partie écrite sur feuille de 45 minutes maximum, à faire en premier, et d'un exercice qui doit être traité ensuite.

Rappel : si ce n'est pas déjà le cas, vous devez configurer le workspace d'Eclipse (File > Switch Workspace) pour qu'il corresponde au répertoire EXAM présent dans le home de votre session de TP noté.

Vérifiez que tous vos fichiers sont bien dans le répertoire EXAM. Tout ce qui n'est pas dans ce répertoire est perdu quand vous vous déconnectez.

La Javadoc 27 est là : https://igm.univ-mlv.fr/~juge/javadoc-27/.

Vous avez le droit de consulter les transparents du cours pendant tout l'examen.

QCM (sur papier)

Moniteur d'activité des threads

Toutes les classes demandées doivent être dans le package fr.uge.concurrence.exo1.

Cet exercice comporte 5 questions.

Dans cet exercice, on se propose de créer une classe thread-safe ThreadMonitor permettant de superviser l'activité des threads d'un programme. Plus précisément, un ThreadMonitor permet de maintenir une liste de threads actifs. À tout moment, un thread peut se signaler (il est actif) : si c'est la première fois, il est ajouté à la fin de la liste ; sinon, il progresse d'une position dans la liste des threads. Ainsi, les plus actifs ont tendance à être en début de liste. À tout moment, on peut connaître l'ordre des threads.

Pour vous aider, on vous fournit un squelette de la classe ThreadMonitor.java, qui contient également un main pour tester votre code.

La classe ThreadMonitor possède un constructeur qui permet de créer un moniteur avec une capacité maximale capacity pour les threads.

La classe ThreadMonitor possède initialement les méthodes suivantes :

Voici un exemple d'utilisation avec un moniteur de capacité 3. Le premier appel à ping() du thread 0 l'ajoute à la liste et ping() renvoie la position 0 (dans un OptionalInt). Le thread 1 appelle ensuite ping() : la liste devient [0, 1] et sa position est 1. Lorsqu'il appelle une seconde fois ping(), il passe devant le thread 0 : la liste devient [1, 0] et sa nouvelle position est 0. Un nouvel appel du thread 1 ne modifie pas la liste puisqu'il est déjà premier et renvoie de nouveau la position 0. Le premier appel du thread 2 l'ajoute à la fin de la liste, qui devient [1, 0, 2], et renvoie la position 2. La capacité du moniteur est désormais atteinte : si le thread 3 appelle ping(), la liste reste inchangée et la méthode renvoie un OptionalInt vide.

Récupérez le code de la classe ThreadMonitor.java et implantez les méthodes ping() et threadOrder() en utilisant des blocs synchronized. Bien entendu, la classe doit être thread-safe (n'oubliez pas de compléter le constructeur).
Pour tester votre code, nous vous fournissons un main dans la classe ThreadMonitor, qui crée un ThreadMonitor d'une capacité de 4 threads. Il démarre 10 threads qui vont signaler leur activité en boucle, avec un petit temps d'attente aléatoire. Le programme n'est pas censé s'arrêter.

Voici un exemple de début d'exécution possible :

thread 7 pings...
thread 0 pings...
thread 0 in position 1
thread 7 in position 0
thread 9 pings...
thread 9 in position 2
thread 5 pings...
thread 5 in position 3
thread 2 pings...
thread 2 leaves (no room left)
thread 8 pings...
thread 8 leaves (no room left)
[Thread[#31,7,5,main], Thread[#24,0,5,main], Thread[#33,9,5,main], Thread[#29,5,5,main]]
thread 6 pings...
thread 6 leaves (no room left)
thread 1 pings...
thread 1 leaves (no room left)
thread 3 pings...
thread 3 leaves (no room left)
thread 4 pings...
thread 4 leaves (no room left)
thread 7 pings...
thread 7 in position 0
thread 0 pings...
thread 5 pings...
thread 0 in position 0
thread 5 in position 2
[Thread[#24,0,5,main], Thread[#31,7,5,main], Thread[#29,5,5,main], Thread[#33,9,5,main]]
thread 9 pings...
thread 9 in position 2
...

On veut maintenant pouvoir augmenter la capacité du moniteur afin d'accepter plus de threads. Désormais, lorsque la capacité maximale est atteinte, la méthode ping() d'un thread qui n'est pas déjà dans la liste bloque jusqu'à ce qu'il y ait de la place ; elle renvoie alors un OptionalInt contenant la position du thread dans la liste. On va également ajouter la méthode makeRoom(), qui permet d'augmenter la capacité maximale du moniteur de 2 si au moins un thread est en attente dans la méthode ping(). Dans ce cas, la méthode renvoie true. Si la capacité n'est pas augmentée, la méthode renvoie false.

Voici un exemple d'utilisation avec un moniteur de capacité initiale 3. Le premier appel à ping() du thread 0 l'ajoute à la liste et ping() renvoie la position 0 (dans un OptionalInt). Le thread 1 appelle ensuite ping() : la liste devient [0, 1] et sa position est 1. Si la méthode makeRoom() est appelée à ce moment-là, elle est sans effet.
Lorsque le thread 1 appelle une seconde fois ping(), il passe devant le thread 0 : la liste devient [1, 0]. Le premier appel du thread 2 l'ajoute à la fin de la liste, qui devient [1, 0, 2], et renvoie la position 2. La capacité du moniteur est désormais atteinte : si le thread 3 appelle ping(), il reste bloqué.
Si la méthode makeRoom() est appelée à ce moment-là, la capacité passe à 5 et le thread 3 est débloqué de son appel à ping(). La liste devient [1, 0, 2, 3] et le thread reçoit la position 3.

Modifiez la méthode ping() et implantez makeRoom() dans la classe ThreadMonitor, qui doit rester thread-safe. Pour tester votre code, vous pouvez dé-commenter la ligne qui appelle makeRoom() dans le main.

Voici un exemple de début d'exécution possible :

thread 9 pings...
thread 9 in position 0
thread 2 pings...
thread 2 in position 1
thread 4 pings...
thread 4 in position 2
[Thread[#33,9,5,main], Thread[#26,2,5,main], Thread[#28,4,5,main]]
** No room needed **
thread 1 pings...
thread 1 in position 3
thread 8 pings...
thread 0 pings...
thread 6 pings...
thread 3 pings...
thread 5 pings...
thread 7 pings...
thread 9 pings...
thread 9 in position 0
[Thread[#33,9,5,main], Thread[#26,2,5,main], Thread[#28,4,5,main], Thread[#25,1,5,main]]
thread 1 pings...
** Room has been made **
thread 1 in position 2
thread 8 in position 4
thread 4 pings...
thread 4 in position 2
thread 2 pings...
thread 2 in position 0
thread 9 pings...
thread 9 in position 0
[Thread[#33,9,5,main], Thread[#26,2,5,main], Thread[#28,4,5,main], Thread[#25,1,5,main], Thread[#32,8,5,main]]
** Room has been made **
thread 0 in position 5
thread 1 pings...
thread 1 in position 2
thread 4 pings...
...

On souhaite modifier le comportement de la méthode makeRoom(), qui permet d'augmenter la capacité maximale du moniteur. Elle continue de renvoyer directement false si aucun thread n'est en attente dans ping(), mais dans le cas contraire, elle bloque tant que le nombre de threads en attente est inférieur à la moitié de la capacité actuelle. Modifiez le code de la classe ThreadMonitor en conséquence.

Dupliquez votre classe ThreadMonitor dans une classe ThreadMonitorRL et réimplantez le même comportement en utilisant un ReentrantLock. Lorsque c'est pertinent, pensez à utiliser des conditions différentes.

Dupliquez votre classe ThreadMonitor de la question 3 dans une classe ThreadMonitorFirstTwo. On souhaite ajouter dans le main un thread qui, toutes les 200 ms (en boucle), essaie de retirer les deux premiers threads du moniteur si leurs ID ont la même parité. L'ID est donné par la méthode d'instance threadId() (c'est la valeur qu'on voit dans le résultat de toString(), celle qui est précédée d'un #).
Modifiez le code pour obtenir le comportement souhaité. Bien entendu, la classe ThreadMonitorFirstTwo doit rester thread-safe.

Voici un exemple de début d'exécution possible (le programme n'est toujours pas censé s'arrêter) :

    
thread 9 pings...
thread 9 in position 0
thread 1 pings...
thread 1 in position 1
removed: [Thread[#33,9,5,main], Thread[#25,1,5,main]]
thread 5 pings...
thread 5 in position 0
thread 0 pings...
thread 0 in position 1
[Thread[#29,5,5,main], Thread[#24,0,5,main]]
** No room needed **
thread 2 pings...
thread 2 in position 2
thread 3 pings...
thread 3 in position 3
thread 8 pings...
thread 4 pings...
thread 6 pings...
thread 7 pings...
thread 0 pings...
thread 0 in position 0
thread 2 pings...
thread 2 in position 1
[Thread[#24,0,5,main], Thread[#26,2,5,main], Thread[#29,5,5,main], Thread[#27,3,5,main]]
** Room has been made **
thread 4 in position 4
thread 6 in position 5
thread 9 pings...
thread 1 pings...
thread 3 pings...
thread 3 in position 2
removed: [Thread[#24,0,5,main], Thread[#26,2,5,main]]
thread 5 pings...
thread 5 in position 0
thread 8 in position 4
thread 7 in position 5
removed: [Thread[#29,5,5,main], Thread[#27,3,5,main]]
removed: [Thread[#28,4,5,main], Thread[#30,6,5,main]]
thread 0 pings...
thread 0 in position 2
thread 1 in position 3
thread 9 in position 4
thread 4 pings...
thread 4 in position 5
[Thread[#32,8,5,main], Thread[#31,7,5,main], Thread[#24,0,5,main], Thread[#25,1,5,main], Thread[#33,9,5,main], Thread[#28,4,5,main]]
** No room needed **
thread 3 pings...
thread 5 pings...
thread 6 pings...
thread 2 pings...
thread 7 pings...
thread 7 in position 0
thread 8 pings...
thread 8 in position 0
...