L'examen est composé de 3 exercices indépendants qui peuvent être traités dans l'ordre que vous voulez.
Rappel : 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 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
.
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
.
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 takeGifts
, 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 inclus.
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 5 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 5 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
.É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 20, 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é N. 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 N 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); recorder.process(1); // => true recorder.process(2); // => true recorder.process(0); // => false recorder.process(3); // => true recorder.process(1); // => false recorder.process(2); // => false // euh ? recorder.process(3); // => true
Donnez une implémentation dans le cas de la capacité 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 capacité fixée à 1, son constructeur ne prend pas de paramètre.On veut maintenant traiter le cas d'une capacité quelconque.
On vous propose de partir de l'implentation assez naive et non-threadsafe ci-dessous. L'idée est de conserver dans le tableau maximums
les N é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 e
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 e
et l'on renvoie vrai et 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); // as the element we process are >=0, we can safely initialize the maxmimums 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 e){ if (e<0){ throw new IllegalArgumentException("Argument must be >=0"); } var indexMin = findIndexOfMinimum(maximums); if (e>maximums[indexMin]){ maximums[indexMin]=e; return true; } return false; } }
var recorder = new MaxRecorderBogus(2); recorder.process(1); // => true maximums = [1,-1] recorder.process(2); // => true maximums = [1,2] recorder.process(0); // => false maximums = [1,2] recorder.process(3); // => true maximums = [3,2] recorder.process(1); // => false maximums = [3,2] recorder.process(2); // => false maximums = [3,2] recorder.process(3); // => true maximums = [3,3]
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
.