Examen 2h sur machine


L'examen est composé de 2 exercices indépendants qui peuvent être traités dans l'ordre que vous voulez.

Rappel : si ce n'est pas déjà fait, 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érifier 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 25 est là : https://igm.univ-mlv.fr/~juge/javadoc-25/.

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

Jeu de mots

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

Cet exercice comporte 4 questions.

Dans cet exercice, on se propose de créer une classe thread-safe WordGuess permettant à plusieurs threads de participer de façon coopérative à un jeu où ils doivent trouver un mot secret en proposant des lettres. Les threads qui veulent jouer doivent commencer par s'inscrire. Quand ils sont assez nombreux, ils reçoivent chacun le même nombre de crédits qu'ils peuvent dépenser pour essayer de deviner des lettres du mot. Si l'un d'entre eux trouve le mot avant qu'ils aient tous dépensé la totalité de leurs crédits, alors tous les threads gagnent le jeu, sinon ils ont tous perdu.

Pour vous aider, on vous fournit un squelette de la classe WordGuess.java, qui contient également un main pour tester votre code. Elle utilise une seconde classe Words.java qui sera décrite plus loin.

La classe WordGuess contient au départ deux champs :

La classe WordGuess possède un constructeur privé qui permet, pour l'instant, d'initialiser ces deux champs, ainsi qu'une factory publique newWordGuess qui permet de créer un newWordGuess pour un nombre de threads donné et s'occupe de tirer la solution au hasard. Vous ajouterez des champs et modifierez le constructeur dans la suite, mais la factory ne devrait pas changer.

Parmi les champs que vous devrez ajouter, il y en a un qui est l'état du jeu, sous la forme d'une String nommée state et qui contiendra les caractères de la solution qui ont déjà été devinés ; les caractères restants à deviner sont des '.'. Pour initialiser l'état, il suffit de lui affecter ".".repeat(solution.length()).

La classe WordGuess possède également les méthodes suivantes :

Récupérez le code de la classe WordGuess.java et implantez les méthodes register et credits en utilisant des blocs synchronized. Vous les utiliserez pour tout cet exercice. Bien entendu, la classe doit être thread-safe. Pour l'instant, vous n'avez pas besoin gérer les interruptions ni d'implanter la méthode play.
Pour tester votre code, nous vous fournissons un main dans la classe WordGuess, qui crée un WordGuess pour 5 threads. Il démarre 6 threads qui vont essayer de s'inscrire après un petit temps d'attente aléatoire. Pour tester la méthode credits, vous pouvez ajouter un thread qui affiche les crédits des threads à intervalles réguliers.

Voici un exemple d’exécution possible :

thread 0 tries to register
thread 4 tries to register
thread 2 tries to register
thread 1 tries to register
thread 5 tries to register
thread 0 registered
thread 5 registered
thread 2 registered
thread 1 registered
thread 4 registered
thread 3 tries to register
thread 3 NOT registered

On veut maintenant pouvoir gérer le cas où un thread serait interrompu alors qu'il est bloqué dans un appel à register. Dans ce cas, tous les threads qui sont en attente dans register doivent également être interrompus et il ne doit plus être possible de s'inscrire pour cette instance de WordGuess. Plus précisément, pour chaque thread en attente dans register, la méthode lève une InterruptedException et pour chaque thread qui essaierait de s'inscrire ensuite, la méthode renvoie false. Implantez ce comportement.

Vous allez maintenant devoir implanter la méthode play de la classe WordGuess. Voici quelques précisions sur son fonctionnement.
Un thread ne peut jouer que s'il lui reste des crédits. Si la lettre jouée n'est pas dans le mot, il perd un crédit. Si la lettre jouée est dans le mot, il ne perd rien et la lettre apparaît dans l'état du jeu à toutes les positions où elle se trouve dans la solution, avec toutes les autres lettres déjà devinées par l'ensemble des threads. Par exemple, si le mot à deviner est "concurrence" et que l'état du jeu est ".on.u..en.e", un thread qui joue la lettre 'c' obtient "concu..ence" et ne perd pas de crédit. S'il avait joué 'i', il aurait perdu un crédit et aurait obtenu ".on.u..en.e". Et s'il avait joué 'e', l'état serait inchangé et il ne perdrait pas de crédit.
Si en jouant une lettre, un thread tombe à 0 crédit, il est bloqué jusqu'à ce que le jeu se termine.
Si le thread n'est pas bloqué, l'Optional renvoyé contient l'état du jeu.
Si plus aucun thread inscrit n'a de crédit, tous les threads sont débloqués et la méthode renvoie un Optional vide pour indiquer qu'ils ont perdu.
Si l'état du jeu ne contient plus aucun '.' (toutes les lettres ont été trouvées), tous les threads sont débloqués et la méthode renvoie un Optional contenant le mot à trouver complet (les threads ont gagné.
Si un thread non inscrit essaie d'appeler la méthode ou si le jeu est terminé, elle doit lever une IllegalStateException.

Pour vous aider, on vous fournit une seconde classe, Words.java qui ne contient que des méthodes statiques pour la manipulation des mots et des caractères. Elle contient les méthodes suivantes. Les 2 premières sont utile pour le code que vous devez écrire et les 3 suivantes sont utilisées par le main.

Implantez la méthode play de la classe WordGuess, qui doit rester thread-safe. Pour tester votre code vous pouvez dé-commenter la partie correspondante dans le main. Chaque thread joue en boucle une lettre qu'il n'a pas déjà jouée et qui n'était pas dans l'état du mot à sa précédente tentative, jusqu'à ce que le jeu soit fini (tous les threads ont soit perdu, soit gagné ensemble).

Voici un exemple d’exécution possible :

thread 0 registered
thread 4 registered
thread 2 registered
thread 3 registered
thread 1 registered
thread 0 plays n
         .....n......n.
thread 1 plays a
         .a...n......n.
thread 2 plays i
         .a...ni.....n.
thread 4 plays j
         .aj..ni.....n.
thread 3 plays b
         .aj..ni.....n.
{Thread[#29,thread 4,5,main]=4, Thread[#26,thread 1,5,main]=4, Thread[#28,thread 3,5,main]=3, Thread[#25,thread 0,5,main]=4, Thread[#27,thread 2,5,main]=4}
thread 1 plays y
         .aj..ni.....n.
thread 2 plays r
         raj..ni.....n.
thread 0 plays r
         raj..ni.....n.
thread 4 plays g
         raj..ni.....n.
thread 1 plays o
         raj..ni.....n.
thread 0 plays e
         raje.ni..e.en.
thread 3 plays s
         raje.nisse.en.
thread 2 plays h
         raje.nisse.en.
thread 4 plays q
         raje.nisse.en.
{Thread[#29,thread 4,5,main]=2, Thread[#26,thread 1,5,main]=2, Thread[#28,thread 3,5,main]=3, Thread[#25,thread 0,5,main]=4, Thread[#27,thread 2,5,main]=3}
thread 3 plays g
         raje.nisse.en.
thread 1 plays g
         raje.nisse.en.
thread 0 plays w
         raje.nisse.en.
thread 2 plays w
         raje.nisse.en.
thread 4 plays m
         raje.nissemen.
thread 1 plays u
         rajeunissemen.
thread 3 plays w
         rajeunissemen.
thread 2 plays k
         rajeunissemen.
thread 0 plays u
         rajeunissemen.
thread 4 plays c
         rajeunissemen.
{Thread[#29,thread 4,5,main]=1, Thread[#26,thread 1,5,main]=1, Thread[#28,thread 3,5,main]=1, Thread[#25,thread 0,5,main]=3, Thread[#27,thread 2,5,main]=1}
thread 3 plays q
thread 1 plays b
thread 0 plays o
         rajeunissemen.
thread 2 plays b
thread 4 plays h
thread 0 plays t
thread 1 WON: rajeunissement
thread 0 WON: rajeunissement
thread 4 WON: rajeunissement
thread 3 WON: rajeunissement
thread 2 WON: rajeunissement

Si un thread est interrompu lors du jeu, on veut que le jeu puisse continuer à se dérouler normalement.
Recopier votre classe WordGuess dans une classe WordGuessWithInterrupt et modifier le code de la méthode play afin qu'un thread interrompu soit retiré du jeu (dans ce cas, la méthode play lève une InterruptedException). La méthode play doit se comporter normalement pour tous les autres threads.
Pour tester , vous pouvez dé-commenter la partie du main précédée du commentaire //Q4 qui fait en sorte que chaque thread demande à s'auto-interrompre avec 1 chance sur 10.

Producteur-consommateur : ConcuResto !

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

Attention, ici, vous devez utiliser le pattern producteur-consommateur et toute autre forme de synchronisation sera considérée comme hors-sujet. Vous n'avez pas non plus le droit d'utiliser un ExecutorService pour cette question.

Dans cet exercice, nous utiliserons une API fictive qui simule le fonctionnement d'un restaurant de delivery Order.java. Le restaurant est (grosso modo) divisé en trois parties : la caisse, les stations de cuisine indépendantes (par exemple, le bar à salades, la station à pizzas, la station à boissons, etc...) et un livreur qui sert les clients.

Écrire le main d'une classe ConcuResto qui utilisera :

Voici un exemple d’exécution possible :
Received Order[id=1, dish=Dish[name=Pizza Margherita, station=Pizza]]
Station Pizza is preparing Dish[name=Pizza Margherita, station=Pizza]
Received Order[id=2, dish=Dish[name=Waikiki, station=HotFood]]
Station HotFood is preparing Dish[name=Waikiki, station=HotFood]
Station Pizza finished Dish[name=Pizza Margherita, station=Pizza]
Received Order[id=3, dish=Dish[name=Chicken Biryani, station=HotFood]]
Station HotFood finished Dish[name=Waikiki, station=HotFood]
Station HotFood is preparing Dish[name=Chicken Biryani, station=HotFood]
Received Order[id=4, dish=Dish[name=Gazpacho, station=ColdPrep]]
Station ColdPrep is preparing Dish[name=Gazpacho, station=ColdPrep]
Received Order[id=5, dish=Dish[name=Ratatouille, station=ColdPrep]]
Station ColdPrep finished Dish[name=Gazpacho, station=ColdPrep]
Station ColdPrep is preparing Dish[name=Ratatouille, station=ColdPrep]
Station HotFood finished Dish[name=Chicken Biryani, station=HotFood]
Received Order[id=6, dish=Dish[name=Pide, station=Pizza]]
Station Pizza is preparing Dish[name=Pide, station=Pizza]
Received Order[id=7, dish=Dish[name=Bouillabaisse, station=HotFood]]
Station HotFood is preparing Dish[name=Bouillabaisse, station=HotFood]
Station ColdPrep finished Dish[name=Ratatouille, station=ColdPrep]
Station Pizza finished Dish[name=Pide, station=Pizza]
Received Order[id=8, dish=Dish[name=Asado, station=Grill]]
Station Grill is preparing Dish[name=Asado, station=Grill]
Station HotFood finished Dish[name=Bouillabaisse, station=HotFood]
Received Order[id=9, dish=Dish[name=Moa Meli, station=HotFood]]
Station HotFood is preparing Dish[name=Moa Meli, station=HotFood]
Station Grill finished Dish[name=Asado, station=Grill]
Served [Order[id=1, dish=Dish[name=Pizza Margherita, station=Pizza]]; Order[id=2, dish=Dish[name=Waikiki, station=HotFood]]; Order[id=4, dish=Dish[name=Gazpacho, station=ColdPrep]]; Order[id=3, dish=Dish[name=Chicken Biryani, station=HotFood]]; Order[id=5, dish=Dish[name=Ratatouille, station=ColdPrep]]]
...

Modifiez votre code de telle sorte que si l'un des threads est interrompu, tout le restaurant s'arrête immédiatement. Pour tester, ajoutez un thread qui interrompt la caisse au bout de 10 secondes.

Nous allons maintenant faire évoluer le comportement afin que le restaurant ferme correctement lorsque le thread de la caisse est interrompu. Par fermer "correctement", on entend que la caisse n'accepte plus de nouvelles commandes et que les autres threads doivent rester actifs aussi longtemps que nécessaire pour livrer les commandes déjà en cours de traitement. La dernière livraison pourra être faite sans avoir atteint Order.DELIVERY_CAPACITY commandes. Lorsque toutes les clients ont reçu leur commande, le restaurant peut fermer.

Indication : vous pouvez modifier le type de vos BlockingQueue afin qu'elle contiennent des valeurs de type Optional. La valeur Optional.empty() indiquera la fin d'un thread aux threads suivants dans la chaîne de production. Une fois que le livreur sait que les cuisines sont fermées, il peut procéder à la livraison même sans avoir reçu Order.DELIVERY_CAPACITY commandes.

Le thread de la caisse peux être interrompu même pendant qu'il envoie les Optional.empty() pour indiquer sa fin. Dans ce cas, tout le restaurant s'arrête immédiatement.