L'examen est composé de 3 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 19 est là : https://igm.univ-mlv.fr/~juge/javadoc-19/.
Vous avez le droit de consulter les transparents du cours pendant l'examen.
Dans cet exercice, on se propose de créer une classe représentant la hotte de Père Noël (Sack). Pour simplifier, on ne mettra dans la hotte que le poids des cadeaux en kilogrammes donc des int. À la création de la hotte, on donnera le poids maximal que la hotte peut porter. Comme toutes les hottes dignes de ce nom, notre hotte devra être thread-safe.
La classe Sack aura les méthodes suivantes :
putGift(int weight) qui essaie de rajouter un cadeau de poids weight et qui bloque si l'ajout du cadeau n'est pas possible.int takeGift() qui prend (donc retire) le dernier cadeau rajouté et renvoie son poids. Si la hotte est vide, la méthode bloque jusqu'à ce qu'il y ait un cadeau à enlever.
Codez la classe thread-safe Sack décrite précédemment en utilisant des ReentrantLock.
Pour tester votre code, vous écrirez un main qui :
Écrire le main demandé.
On veut maintenant rajouter une méthode List<Integer> takeGiftsUntil(int weight) qui va prendre (donc retirer) des cadeaux dans la hotte en commençant toujours par le plus récemment ajouté jusqu'à avoir pris au moins weight kilogrammes de cadeaux. Dès que le poids est atteint ou dépassé, la méthode renvoie la liste des poids des cadeaux retirés. La méthode prend les cadeaux dès qu'elle le peut et attend d'avoir au moins le poids total demandé. On pourra ainsi demander 1000 kg de cadeaux dans une hotte de 500 kg. Autrement dit, la méthode prend des cadeaux puis, si nécessaire, attend que de nouveaux cadeaux soient rajoutés pour continuer.
On veut aussi rajouter une méthode int weightNeeded() qui renvoie la somme des poids encore nécessaires pour satisfaire tous les threads qui sont en train d'exécuter la méthode takeGiftsUntil.
Rajoutez les deux méthodes demandées à votre classe Sack.
Dans le main, on va en plus du code précédent,
takeGiftsUntil(1000) et ensuite affichent la somme des poids des cadeaux ainsi recupérés
weightNeeded().
Rajoutez les threads demandés dans votre main.
On veut maintenant spécifier le comportement de nos méthodes en cas d'interruption.
Si les méthodes putGift et takeGift sont interrompues, elles lèvent une InterruptedException.
Si la méthode takeGiftsUntil est interrompue, elle ne s'arrête pas, mais elle devient prioritaire pour retirer des cadeaux. Tant qu'elle n'a pas fini, aucun autre thread ne peut retirer de cadeau : si un thread est en train d'appeler une des méthodes takeGiftsUntil ou takeGift, ces méthodes sont bloquées en attendant que cette méthode soit terminée).
Si d'autres threads ont été interrompus pendant qu'ils appelaient la méthode takeGiftsUntil, ils sont eux aussi prioritaires et peuvent continuer à enlever des paquets. Pour signaler que le thread a été interrompu, la méthode takeGiftsUntil positionnera le statut d'interruption avant de renvoyer la liste des poids des cadeaux.
Copiez votre classe Sack dans une classe SackWithInterruption est implanter la gestion des interruptions demandée.
Dans cet exercice, on vous fournit une petite API factice qui permet d'attraper des Pokemon, de les mettre dans des Pokeball et de mettre plusieurs Pokeball dans une boîte (Crate). Le code de l'API est dans PokeAPI.java. Vous n'avez pas besoin de lire le code de l'API, toutes les informations nécessaires sont disponibles dans la brève description ci-dessous.
Un Pokemon possède un nom (name) et une rareté (rarity) comprise entre 0 et 5.
public record Pokemon(String name, int rarity){..}
La méthode static Pokemon capture() permet d'attraper un Pokemon.
Une Pokeball possède un Pokemon pokemon et une valeur (value) entre 0 et 10.
public record Pokeball(Pokemon pokemon, int value){..}
La méthode static Pokeball trap(Pokemon pokemon) permet de mettre un Pokemon dans une Pokeball.
Une Crate contient au plus 6 Pokeball qui ont toutes la même valeur. Elle est crée grâce à la méthode static Crate box(List<Pokeball> pokeballs).
Le code ci-dessous donne un exemple d'utilisation de l'API.
public static void main(String[] args) throws InterruptedException {
var pokemon = PokeAPI.capture();
System.out.println(pokemon); // Pokemon[name=Ronflex, rarity=1]
var pokeball = PokeAPI.trap(pokemon);
System.out.println(pokeball); // Pokeball[pokemon=Pokemon[name=Ronflex, rarity=1], value=2]
var crate = PokeAPI.box(List.of(pokeball));
System.out.println(crate); // Crate[content=[Pokeball[pokemon=Pokemon[name=Ronflex, rarity=1], value=2]]]
}
Dans cet exercice, on vous demande dans le main d'une classe PokemonFactory de :
Pokemon en utilisant la méthode PokeAPI.capture,Pokemon capturés dans des Pokeball avec la méthode PokeAPI.trap. L'un des threads s'occupera des Pokemon dont la rareté est comprise entre 0 et 4 et l'autre, des Pokemon de rareté 5.Pokeball, on démarrera deux threads chargés de mettre en Crate les Pokeball de la valeur correspondante. Ces threads attendrons d'avoir 6 Pokeball pour créer une Crate avec la méthode PokeAPI.box et afficheront la Crate produite avant de passer à la confection d'une nouvelle Crate. Tous les threads doivent afficher leur nom et leurs actions.
Écrivez la classe PokeFactory demandée.
On veut maintenant que, lorsque que l'on a réussit à produire la première Crate
de Pokeball de valeur 10, le programme s'arrête. On demande que les threads qui produisent les Crate produisent une dernière Crate, même incomplète, et l'affiche avant de s'arrêter.
Copiez votre code dans une classe PokeFactoryStop et modifier le main pour obtenir le comportement demandé.
Dans cet exercice, on cherche à créer une classe MaxRecorder thread-safe que l'on crée avec une certaine capacité size. Cette classe possède une méthode process qui prend en paramètre un entier positif et qui renvoie vrai si l'entier proposé fait partie des size plus grands nombres qui ont été proposés lors d'appels à la méthode process depuis la création de la classe.
var recorder = new MaxRecorder(2); System.out.println(recorder.process(1)); // => true System.out.println(recorder.process(2)); // => true System.out.println(recorder.process(0)); // => false System.out.println(recorder.process(5)); // => true System.out.println(recorder.process(4)); // => true System.out.println(recorder.process(3)); // => false
Normalement, pour gérer les cas d'égalité, on compare d'abord les valeurs
et si elles sont égales, on considère comme plus grande valeur, celle qui a été proposée le plus tôt.
Mais pour simplifier l'exercice, on supposera que la méthode process n'est jamais appelée deux fois avec exactement la même valeur. Vous n'avez pas à le vérifier et cela vous évitera de vous arrachez les cheveux sur les cas d'égalité.
Donnez une implémentation thread-safe dans le cas où size vaut 1 dans une classe MaxRecorderOne qui n'utilise ni section critique, ni verrou, en utilisant la classe VarHandle.
process va simplement dire si la valeur qui lui est passée en argument est la plus grande vue jusqu'à présent.process n'accepte en argument que des entiers positifs, ce qui peut légèrement simplifier l'implantation.MaxRecorderOne a une size fixée à 1, son constructeur ne prend pas de paramètre.On veut maintenant traiter le cas d'une capacité positive quelconque.
On vous propose de partir de l’implantation assez naïve et non-threadsafe ci-dessous. L'idée est de conserver dans le tableau maximums les size éléments maximums rencontrés jusque là. Comme process n'accepte que des entiers positifs, initialement toutes les cases du tableau maximums peuvent être initialisée à -1. La méthode process(e) cherche le plus petit des maximums vus et le compare à la valeur value qui lui est passée en paramètre. Si la valeur passée est plus grande, on remplace la plus petite valeur du tableau maximums par le paramètre value et on renvoie vrai, sinon on renvoie faux.
import java.util.Arrays;
public class MaxRecorderBogus {
private final int[] maximums;
public MaxRecorderBogus(int size) {
if (size < 1) {
throw new IllegalArgumentException("Invalid size");
}
this.maximums = new int[size];
Arrays.setAll(maximums, __ -> -1); // the elements processed are >=0, we can safely initialize with -1
}
/**
* Return the index of the first occurrence of the minimal element in a
* non-empty array
*/
private static int findIndexOfMinimum(int[] t) {
if (t.length == 0) {
throw new IllegalArgumentException();
}
var min = t[0];
var index = 0;
for (var i = 1; i < t.length; i++) {
if (t[i] < min) {
min = t[i];
index = i;
}
}
return index;
}
public boolean process(int value) {
if (value < 0) {
throw new IllegalArgumentException("Argument must be >=0");
}
var indexMin = findIndexOfMinimum(maximums);
if (value > maximums[indexMin]) {
maximums[indexMin] = value;
return true;
}
return false;
}
}
var recorder = new MaxRecorder(2); System.out.println(recorder.process(1)); // => true maximums = [1,-1] System.out.println(recorder.process(2)); // => true maximums = [1,2] System.out.println(recorder.process(0)); // => false maximums = [1,2] System.out.println(recorder.process(5)); // => true maximums = [5,2] System.out.println(recorder.process(4)); // => true maximums = [5,4] System.out.println(recorder.process(3)); // => false maximums = [5,4]
On veut une implantation thread-safe qui n'utilise ni section critique, ni verrou. Il vous faudra donc un accès volatile aux différentes cases du tableau maximums. Pour cela, vous utiliserez la classe AtomicIntegerArray.
Implémenter cette version dans une classe MaxRecorderLockFree.