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.
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 :
public final static int MAX = 1000; la valeur maximum d'un thread.
public final static int PLAYERS_PER_TEAM = 5; le nombre de threads par équipe.
public enum STATUS {NOT_IN_TEAM, IN_TEAM, CAPTAIN, WINNER} qui permettra d'indiquer leur statut aux threads qui participent à l'enchère.
private final LinkedHashMap<String, ArrayList<Thread>> teams; c'est la structure de données qui va vous permettre de conserver les équipes associées à leur nom. Le capitaine d'une équipe est obligatoirement le premier thread de la liste.
À 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).
List<String> waitingTeams() : elle renvoie une liste des noms d'équipes dans lesquelles il reste de la place. Si toutes les équipes sont au complet, elle renvoie null. S'il ne reste de place dans aucune des équipes existantes mais qu'on peut encore créer une équipe, elle renvoie une liste vide.
STATUS bidForTeam(String name) : elle permet de participer à l'enchère pour l'équipe dont le nom est donné en paramètre. Si aucune équipe de ce nom n'existe et qu'il reste de la place pour une nouvelle équipe, le thread qui appelle la méthode en devient le capitaine. Tout thread qui s'inscrit pour une équipe est bloqué jusqu'à ce que l'équipe soit au complet. À ce moment là, la méthode renvoie CAPTAIN pour le capitaine et IN_TEAM pour tous les autres membres de l'équipe. Si la méthode est appelée alors qu'il n'y a plus de place dans l'équipe ou pour créer l'équipe, la méthode renvoie NOT_IN_TEAM.
Set<Thread> result(int refereeValue) : elle permet à un thread d'être arbitre de l'enchère pour savoir quelle équipe a gagné. Sa spécification sera donnée dans la dernière question.
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
InterrupteException,
TeamBidding sont interrompus. Il n'est alors plus possible d’utiliser ce TeamBidding, c'est à dire que tous les appels à ses méthodes doivent désormais lever une IllegalStateException.
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();
}
});
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 :
EasterEgg hunt() simule la recherche d'un œuf,Bag bag(List<EasterEgg> eggs) prend une liste de 10 œufs et renvoie un sac Bag contenant ces œufs,int sell(Bag bag) qui simule la vente d'un sac d'œufs de Pâques et qui renvoie le prix de vente.Dans cet exercice, vous devez utiliser un producteur-consommateur dans le main d'une classe Applicationpour faire communiquer les threads suivants :
Easter.hunt,location dans Location, un thread qui va créer en boucle des sacs contenant 10 œufs venant de cette location,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é.