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 21 est là : https://igm.univ-mlv.fr/~juge/javadoc-21/.

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

Enchère par équipe

Dans cet exercice, on se propose de créer une classe TeamBidding permettant à plusieurs threads de participer à une enchère, par équipes de 5. Bien entendu, dans tout l'exercice la (ou les) classe(s) que vous écrirez doivent être thread-safe.

La classe TeamBidding doit avoir un constructeur qui prend en paramètre le nombre d'équipes pouvant participer. Chaque équipe est désignée par un nom. Lorsqu'un thread veut participer à l'enchère, il donne le nom de l'équipe qu'il souhaite rejoindre, soit en tant que capitaine, soit en tant que simple co-équipier. Quand il rejoint une équipe, il est bloqué jusqu'à ce que l'équipe soit au complet. La classe TeamBidding doit posséder les champs suivants :

À tout moment, un thread peut demander un liste des noms d'équipes où il reste de la place.
Enfin, pour gagner l’enchère, il faut être l'équipe dont la valeur est la plus proche de celle choisie par l'arbitre.

La classe TeamBidding possédera également méthodes suivantes (qui seront écrites au fur et à mesure des questions).

Implanter la classe thread-safe TeamBidding décrite précédemment avec ses méthodes waitingTeams et bidForTeam, en utilisant des ReentrantLock. Vous les utiliserez pour tout cet exercice.
Pour tester votre code, nous vous fournissons un main dans la classe TeamBiddingTest.java qui crée un TeamBidding pour 5 équipes et démarre 30 threads qui essaient d'y participer. S'il n'y a plus de place, le thread s'arrête, et s'il n'y a pas d'équipe avec de la place, il essaie de devenir capitaine d'une nouvelle équipe.

Voici un exemple d’exécution possible :

Thread-18 : no team available, tries to be captain
Thread-19 : no team available, tries to be captain
Thread-26 : no team available, tries to be captain
Thread-4 : no team available, tries to be captain
Thread-28 : no team available, tries to be captain
Thread-26 asks for Team of Thread-26
Thread-28 asks for Team of Thread-28
Thread-4 asks for Team of Thread-4
Thread-19 asks for Team of Thread-19
Thread-18 asks for Team of Thread-18
Thread-16 asks for Team of Thread-4
Thread-9 asks for Team of Thread-28
Thread-21 asks for Team of Thread-4
Thread-0 asks for Team of Thread-19
Thread-10 asks for Team of Thread-28
Thread-17 asks for Team of Thread-26
Thread-2 asks for Team of Thread-26
Thread-7 asks for Team of Thread-28
Thread-14 asks for Team of Thread-28
Thread-14 is enroled in Team of Thread-28
Thread-28 is enroled in Team of Thread-28
Thread-10 is enroled in Team of Thread-28
Thread-7 is enroled in Team of Thread-28
Thread-9 is enroled in Team of Thread-28
Thread-5 asks for Team of Thread-26
Thread-27 asks for Team of Thread-4
Thread-11 asks for Team of Thread-19
Thread-15 asks for Team of Thread-4
Thread-15 is enroled in Team of Thread-4
Thread-4 is enroled in Team of Thread-4
Thread-16 is enroled in Team of Thread-4
Thread-21 is enroled in Team of Thread-4
Thread-27 is enroled in Team of Thread-4
Thread-29 asks for Team of Thread-18
Thread-12 asks for Team of Thread-19
Thread-13 asks for Team of Thread-26
Thread-13 is enroled in Team of Thread-26
Thread-2 is enroled in Team of Thread-26
Thread-26 is enroled in Team of Thread-26
Thread-17 is enroled in Team of Thread-26
Thread-5 is enroled in Team of Thread-26
Thread-6 asks for Team of Thread-18
Thread-3 asks for Team of Thread-18
Thread-1 asks for Team of Thread-19
Thread-19 is enroled in Team of Thread-19
Thread-1 is enroled in Team of Thread-19
Thread-11 is enroled in Team of Thread-19
Thread-0 is enroled in Team of Thread-19
Thread-12 is enroled in Team of Thread-19
Thread-22 asks for Team of Thread-18
Thread-22 is enroled in Team of Thread-18
Thread-18 is enroled in Team of Thread-18
Thread-6 is enroled in Team of Thread-18
Thread-3 is enroled in Team of Thread-18
Thread-29 is enroled in Team of Thread-18
Thread-23 stops, all teams are  full
Thread-8 stops, all teams are  full
Thread-25 stops, all teams are  full
Thread-20 stops, all teams are  full
Thread-24 stops, all teams are  full

Les deux questions suivantes sont indépendantes

Après avoir recopié votre classe TeamBidding dans une classe TeamBiddingWithInterrupt, modifier votre code pour que si un thread est interrompu lorsqu'il est en attente dans bidForTeam, alors

Pour tester, modifier le code du main dans une classe TeamBiddingWithInterruptTest pour que les 5 premiers threads participant à l'enchère soient interrompus au bout de 200 millisecondes. Le programme doit s'arrêter.

Après avoir recopié votre classe TeamBidding (de la première question) dans une classe TeamBiddingWithReferee, on va ajouter la méthode suivante :
List<Thread> result(int refereeValue) : elle permet à un thread d'être arbitre de l'enchère et de déterminer l'équipe gagnante en fonction de sa valeur. Elle prend en paramètre la valeur à atteindre et permet d'attendre le résultat de l'enchère. Lorsque toutes les équipes sont au complet, elle renvoie la liste des threads de l'équipe gagnante. Le premier thread de la liste est le capitaine.
La valeur d'une équipe team de type List<Thread> est grosso modo la somme des hashCode() de ses thread, elle est calculée ainsi : team.stream().mapToInt(x -> x.hashCode() % MAX).sum(). L'équipe gagnante est celle dont la valeur est la plus proche de refereeValue. En cas d'égalité, c'est l'équipe dont le capitaine est arrivé en premier qui gagne.
Attention, cette méthode modifie également la sémantique de bidForTeam pour les threads capitaines (et uniquement pour eux). Désormais, lorsqu'un thread est capitaine, la méthode attend qu'un arbitre ait demandé le résultat et elle renvoie WINNER pour le capitaine de l'équipe gagnante. Pour les autres capitaines, elle renvoie toujours CAPTAIN.
Il y a un bonus si vous utilisez bien les Conditions des ReentrantLock pour éviter les réveils de threads inutiles.

Si vous le souhaitez, vous pouvez utiliser le code ci-dessous pour calculer l'équipe gagnante, une fois toutes les équipes complétées.

    private int score(List<Thread> team, int value){
        return Math.abs(team.stream().mapToInt(x -> x.hashCode() % MAX).sum()-value);
    }
    
    private String computeWinningTeam(int value){
        return teams.entrySet().stream().min(Comparator.comparingInt(entry -> score(entry.getValue(),value))).orElseThrow().getKey();
    }    

Pour tester TeamBiddingWithReferee, vous pourrez compléter le code du main dans une classe TeamBiddingWithRefereeTest en ajoutant le thread suivant :

Thread.ofPlatform().start(() -> {
    try {
        var value = ThreadLocalRandom.current().nextInt(MAX) * (1 + PLAYERS_PER_TEAM);
        var winner = teamBiddig.result(value);
        System.out.println("------------> The winner for value " + value + " is team "
                    + winner.stream().map(Thread::getName).collect(Collectors.joining(", ", "[", "]")));
    } catch (InterruptedException e) {
        throw new AssertionError();
    }
}); 

EasterEggs

Dans cet exercice, on vous fournit une petite API factice Easter.java qui simule une chasse aux œufs de Pâques. Le résumé ci-dessous est suffisant pour faire cet exercice et il n'est pas nécessaire de lire le code de Easter.

Dans cet exercice, vous devez utiliser le pattern producteur-consommateur et toute autre forme de synchronisation sera considérée comme hors-sujet.

Un œuf de Pâques EasterEgg possède un identifiant unique (id), le lieu où il a été trouvé (location) et un booléen (golden) pour indiquer s'il s'agit d'un œuf doré. Les différents lieux où l'on peut avoir trouvé l'œuf enum Location sont dans la classe Easter.

public record EasterEgg(int id, Location location){..}    
public enum Location {HOUSE, TREE, SHED, FLOWER_BANK, GARDEN, GRASS} 

Le record Bag représente un sac contenant exactement 10 œufs.

L'API Easter propose 3 méthodes statiques :

Dans cet exercice, vous devez utiliser un producteur-consommateur dans le main d'une classe Applicationpour faire communiquer les threads suivants :

Tous les threads doivent afficher leurs actions de manière à obtenir un affichage semblable à celui donné ci-dessous :

Hunter thread 1 found the egg EasterEgg[id=337659315296214638, location=TREE]
Hunter thread 2 found the egg EasterEgg[id=7211146961493132884, location=TREE]
Hunter thread 0 found the egg EasterEgg[id=3152195007267684218, location=SHED]
Hunter thread 1 found the egg EasterEgg[id=1988141488470773425, location=GRASS]
Hunter thread 2 found the egg EasterEgg[id=8362414027292348301, location=GARDEN]
Hunter thread 0 found the egg EasterEgg[id=9004725865679010577, location=TREE]
Hunter thread 2 found the egg EasterEgg[id=3164153089946274274, location=HOUSE]
Hunter thread 0 found the egg EasterEgg[id=3759589384194678846, location=SHED]
Hunter thread 1 found the egg EasterEgg[id=388951965742599739, location=FLOWER_BANK]
Hunter thread 2 found the egg EasterEgg[id=2982713580648209724, location=SHED]
Hunter thread 0 found the egg EasterEgg[id=3208574649686206053, location=TREE]
Hunter thread 1 found the egg EasterEgg[id=283231427923289137, location=SHED]
Hunter thread 2 found the egg EasterEgg[id=2864050980523311990, location=GRASS]
Hunter thread 1 found the egg EasterEgg[id=5330868978931351633, location=FLOWER_BANK]
Hunter thread 0 found the egg EasterEgg[id=820087410415900254, location=TREE]
Hunter thread 2 found the egg EasterEgg[id=7640943586498591914, location=GARDEN]
Hunter thread 0 found the egg EasterEgg[id=1374020639885391966, location=GRASS]
Hunter thread 1 found the egg EasterEgg[id=2480553831621670283, location=GARDEN]
Hunter thread 2 found the egg EasterEgg[id=4349019296579215896, location=GARDEN]
Hunter thread 0 found the egg EasterEgg[id=8115968026739831217, location=GARDEN]
Hunter thread 1 found the egg EasterEgg[id=8734388956883732814, location=SHED]
Hunter thread 2 found the egg EasterEgg[id=7973671789782512278, location=SHED]
Hunter thread 0 found the egg EasterEgg[id=455200858885068447, location=SHED]
Hunter thread 1 found the egg EasterEgg[id=635797806978681390, location=GRASS]
Hunter thread 2 found the egg EasterEgg[id=7276520852774756431, location=SHED]
Hunter thread 1 found the egg EasterEgg[id=8418559985867193466, location=SHED]
Hunter thread 0 found the egg EasterEgg[id=4082299786478816591, location=TREE]
Hunter thread 2 found the egg EasterEgg[id=3063433003196588987, location=GRASS]
Hunter thread 1 found the egg EasterEgg[id=2051311135837518404, location=TREE]
Hunter thread 0 found the egg EasterEgg[id=2719507417948935971, location=SHED]
Hunter thread 2 found the egg EasterEgg[id=2567161717929455624, location=GRASS]
Hunter thread 0 found the egg EasterEgg[id=5497643419919839678, location=TREE]
Hunter thread 1 found the egg EasterEgg[id=5232165255553244551, location=TREE]
Bagger thread for location SHED created the bag Bag[content=[EasterEgg[id=3152195007267684218, location=SHED], EasterEgg[id=3759589384194678846, location=SHED], EasterEgg[id=2982713580648209724, location=SHED], EasterEgg[id=283231427923289137, location=SHED], EasterEgg[id=8734388956883732814, location=SHED], EasterEgg[id=7973671789782512278, location=SHED], EasterEgg[id=455200858885068447, location=SHED], EasterEgg[id=7276520852774756431, location=SHED], EasterEgg[id=8418559985867193466, location=SHED], EasterEgg[id=2719507417948935971, location=SHED]]]
Hunter thread 2 found the egg EasterEgg[id=7160336255813411201, location=GRASS]
Hunter thread 0 found the egg EasterEgg[id=4659513974383425080, location=GARDEN]
Hunter thread 1 found the egg EasterEgg[id=7523649814982368554, location=GRASS]
Hunter thread 2 found the egg EasterEgg[id=1030700595023613333, location=SHED]
Hunter thread 0 found the egg EasterEgg[id=8804939843728284410, location=GARDEN]
Hunter thread 1 found the egg EasterEgg[id=6473697207157557636, location=GARDEN]
Seller thread 0 sold the bag Bag[content=[EasterEgg[id=3152195007267684218, location=SHED], EasterEgg[id=3759589384194678846, location=SHED], EasterEgg[id=2982713580648209724, location=SHED], EasterEgg[id=283231427923289137, location=SHED], EasterEgg[id=8734388956883732814, location=SHED], EasterEgg[id=7973671789782512278, location=SHED], EasterEgg[id=455200858885068447, location=SHED], EasterEgg[id=7276520852774756431, location=SHED], EasterEgg[id=8418559985867193466, location=SHED], EasterEgg[id=2719507417948935971, location=SHED]]] for 6
...

Écrire la classe Application demandée.

On va maintenant faire évoluer notre application. On veut arrêter l'application quand on aura vendu pour plus de 1_000 euros de sacs d'œufs. Vous devez prendre en compte les ventes faites par les deux threads vendeurs et vous devez utilisez le pattern producteur-consommateur pour gérer la concurrence.
Indice : Il peut être nécessaire de rajouter un thread supplémentaire.

Copier votre code dans une classe ApplicationStop et modifier le main pour obtenir le comportement demandé.