Rappel : si ce n'est pas déjà le cas, 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.
Vous avez le droit de consulter les transparents du cours pendant tout l'examen.
La Javadoc est là : javadoc 21.
Dans cet exercice, on cherche à simuler un SecretSanta
qui permet à plusieurs threads de faire un père Noël secret en s’inspirant de ce principe. Toutes les classes demandées doivent être dans le package secretsanta
. Dans tout l'exercice, on vous demande d'utiliser l'API des ReentrantLock
.
La classe SecretSanta
est créé pour un nombre fixé nbParticipants de participants pris en paramètre à la construction. On fixe également à la création, l'ordre dans lequel les différents participants vont retirer les noms du chapeau (donc les participants ne prennent pas un nom au hasard). Pour cela, on donne une liste de nbParticipants
entiers. Le i-ème entier de cette liste vaut j pour indiquer que le (i+1)-ème thread à avoir déposé un nom dans le chapeau doit retirer le nom qui a été mis par le (j+1)-ème thread. Ainsi, en créant un SecretSanta
avec les paramètres suivants :
var secretSanta = new SecretSanta(4, List.of(2, 3, 1, 0));
On obtient que le premier thread (celui d'indice 0) qui met un nom prendra le nom donné par le 3-ème thread. Le deuxième celui du 4-ème, le 3-ème celui du second et le 4-ème celui du premier.
Pour l'instant, la classe SecretSanta
possédera les méthodes publiques suivantes :
SecretSanta(int nbParticipants, List<Interger> order)
permet de construire un SecretSanta
pour nbParticipants
threads avec l'ordre de retrait donné par la liste order
.submit(String value)
qui permet à un thread de mettre un nom dans le chapeau, attend que tous les participants ait donné un nom et renvoie le nom déposé par un des participants comme expliqué plus haut. Pour l'instant, si le chapeau est plein au moment de l'appel à submit
, la méthode lève une IllegalStateException
.En utilisant l'API des ReentrantLock
, écrire une classe thread-safe SecretSanta
comme décrite précédemment.
Pour tester votre code, vous pouvez copier le main
ci-dessous.
public static void main(String[] args) { var secretSanta = new SecretSanta(5, List.of(0, 1, 3, 4, 2)); int[] waitingTimes = {500, 1000, 1500, 2000, 2500}; for (var id = 0; id < waitingTimes.length; id++) { var waitingTime = waitingTimes[id]; Thread.ofPlatform().name("Thread " + id).start(() -> { try { Thread.sleep(waitingTime); var currentThreadName = Thread.currentThread().getName(); System.out.println(currentThreadName + " received " + secretSanta.submit(currentThreadName)); } catch (InterruptedException e) { throw new AssertionError(e); } }); } }
Vous devriez observer les lignes ci-dessous mais possiblement dans un autre ordre d'affichage.
Thread 4 received Thread 2 Thread 0 received Thread 0 Thread 3 received Thread 4 Thread 2 received Thread 3 Thread 1 received Thread 1
On veut maintenant rajouter une méthode List<String> observe(int n)
qui attend que n
noms aient été proposés et renvoie la liste des n
premiers noms proposés. Si un thread appelant observe
est interrompu, on veut que ce thread lève une InterruptedException
et que tous les threads en attente dans un appel à observe
lèvent une InterruptedException
. Si un thread tente d'appeler observe
à l'avenir, il devra lever une IllegalStateException
. Attention, les threads qui appellent submit
ne doivent pas être affectés.
Rajouter la méthode observe
décrite ci-dessus à votre classe SecretSanta
.
Pour tester votre code, vous pouvez rajouter le code suivant dans le main
de votre classe Application
:
int[] observedSizes = {1,2,3,4,5}; var observers = new Thread[observedSizes.length]; for (var id = 0; id < observedSizes.length; id++) { var observedSize = observedSizes[id]; observers[id]=Thread.ofPlatform().name("Observer " + id).start(() -> { var observerName = Thread.currentThread().getName(); try { System.out.println(observerName + " observed " + secretSanta.observe(observedSize)); } catch (InterruptedException e) { System.out.println(observerName + " did not observe any thing as a InterruptedException was thrown"); } }); } Thread.sleep(1100); observers[2].interrupt();
Vous devriez voir un affichage du genre (notez que l'ordre des lignes n'est pas garanti) :
Observer 0 observed [Thread 0] Observer 1 observed [Thread 0, Thread 1] Observer 2 did not observe any thing as a InterruptedException was thrown Observer 3 did not observe any thing as a InterruptedException was thrown Observer 4 did not observe any thing as a InterruptedException was thrown Thread 2 received Thread 3 Thread 3 received Thread 4 Thread 0 received Thread 0 Thread 4 received Thread 2 Thread 1 received Thread 1
Les deux questions suivantes sont indépendantes les unes des autres.
On veut maintenant rendre la classe SecretSanta
réutilisable.
Copier la classe SecretSanta
dans une classe SecretSantaReusable
et rendre la classe réutilisable. Plus précisément, la méthode submit
ne lèvera pas d'exception s'il y a déjà nbParticipants
noms qui ont été donnés, mais elle attendra que tous les threads ait retiré un nom pour utiliser l'objet.
Vous pouvez tester votre code avec le main
suivant :
var nbParticipants = 5; var secretSantaReusable = new SecretSantaReusable(nbParticipants, List.of(1, 2, 3, 4, 0)); for (var id = 0; id < nbParticipants * 3; id++) { Thread.ofPlatform().name("Thread " + id).start(() -> { try { var currentThreadName = Thread.currentThread().getName(); System.out.println(currentThreadName + " received " + secretSantaReusable.submit(currentThreadName)); } catch (InterruptedException e) { throw new AssertionError(e); } }); }
Vous devriez obtenir un affichage du type suivant :
Thread 9 received Thread 7 Thread 8 received Thread 5 Thread 11 received Thread 13 Thread 7 received Thread 8 Thread 10 received Thread 12 Thread 6 received Thread 9 Thread 0 received Thread 1 Thread 13 received Thread 14 Thread 14 received Thread 10 Thread 5 received Thread 6 Thread 3 received Thread 4 Thread 12 received Thread 11 Thread 1 received Thread 2 Thread 4 received Thread 0 Thread 2 received Thread 3
Vérifiez que le main
termine et que les threads échangent bien de manière circulaire par 5. Dans notre exemple, on a :
9 -> 7 -> 8 -> 5 -> 6 -> 9 11 -> 13 -> 14 -> 10 -> 12 0 -> 1 -> 2 -> 3 -> 4 -> 0
On repart maintenant de la classe SecretSanta
obtenue à la question 2. Vous copierez son code dans une classe SecretSantaInterrupt
.
On veut modifier la méthode submit
pour que, si un thread est interrompu pendant qu'il attend dans submit
, la méthode lève une InterruptedException
et tout se passe comme si ce thread n'avait jamais proposé de nom.
Après avoir recopié votre classe SecretSanta
dans une classe SecretSantaInterrupt
, modifier la méthode submit
pour obtenir le comportement demandé.
Vous tester votre code, vous pouvez utiliser le main
suivant :
public static void main(String[] args) throws InterruptedException { var secretSanta = new SecretSantaInterrupt(5, List.of(0, 1, 3, 4, 2)); int[] waitingTimes = {500, 1_000, 1_500, 2_000, 2_500, 3_000}; var threads = new Thread[waitingTimes.length]; for (var id = 0; id < waitingTimes.length; id++) { var waitingTime = waitingTimes[id]; threads[id]=Thread.ofPlatform().name("Thread " + id).start(() -> { var currentThreadName = Thread.currentThread().getName(); try { Thread.sleep(waitingTime); System.out.println(currentThreadName + " received " + secretSanta.submit(currentThreadName)); } catch (InterruptedException e) { System.out.println(currentThreadName + " has been stopped"); } }); } Thread.sleep(1_600); threads[2].interrupt(); }
Vous devriez voir l'affichage suivant (l'ordre des lignes n'est pas garanti) :
Thread 2 has been stopped Thread 4 received Thread 5 Thread 5 received Thread 3 Thread 0 received Thread 0 Thread 1 received Thread 1 Thread 3 received Thread 4
Toutes les classes demandées doivent être dans le package banco
.
Dans cet exercice, on utilise une API fictive qui permet de récupérer des transferts bancaires et de tester s'ils sont suspicieux.
L'API est téléchargeable ici Banco.java. Elle définit :
Bank
qui liste les banques connues par cette API,public record WireTransfer(int bankAccount, Bank bank, int amount)
représentant un transfert bancaire vers un compte d'une banque pour un certain montant,WireTransfer retrieveWireTransfer()
qui récupère un transfert bancaire depuis un serveur,boolean isSuspect(WireTransfer wireTransfer)
qui teste si un virement est suspicieux.Le but de l'exercice est d'écrire une classe permettant de récupérer des transferts bancaires, de tester s'ils sont suspicieux, et d'afficher pour chaque banque la somme des montants des transferts pour cette banque. Pour paralléliser la tâche, nous allons utiliser plusieurs threads.
Dans cet exercice, on vous demande d'utiliser le pattern producteur-consommateur comme seul moyen de synchronisation (en particulier, pas de join
).
Plus précisément, on souhaite avoir :
retrieveWireTransfer()
Banco.Bank
, un thread sera dédié à afficher, en boucle, la somme courante des montants des virements pour cette banque.On souhaite un affichage du type suivant :
---> WireTransfer[bankAccount=986115, bank=PICSOUBANK, amount=8314] ---> WireTransfer[bankAccount=23561, bank=DESSOUSSOUS, amount=-5077] ---> WireTransfer[bankAccount=183500, bank=MASSETHUNE, amount=8247] PICSOUBANK has 8314 after WireTransfer[bankAccount=986115, bank=PICSOUBANK, amount=8314] ---> WireTransfer[bankAccount=204725, bank=PICSOUBANK, amount=-6448] MASSETHUNE has 8247 after WireTransfer[bankAccount=183500, bank=MASSETHUNE, amount=8247] ---> WireTransfer[bankAccount=428714, bank=MASSETHUNE, amount=-7375] PICSOUBANK has 1866 after WireTransfer[bankAccount=204725, bank=PICSOUBANK, amount=-6448] DESSOUSSOUS has -5077 after WireTransfer[bankAccount=23561, bank=DESSOUSSOUS, amount=-5077] ---> WireTransfer[bankAccount=932031, bank=BLE, amount=-8795] ---> WireTransfer[bankAccount=694391, bank=BLE, amount=-9551] ---> WireTransfer[bankAccount=446291, bank=OSEILLE, amount=4957] BLE has -8795 after WireTransfer[bankAccount=932031, bank=BLE, amount=-8795] MASSETHUNE has 872 after WireTransfer[bankAccount=428714, bank=MASSETHUNE, amount=-7375] ---> WireTransfer[bankAccount=636732, bank=DESSOUSSOUS, amount=-315] OSEILLE has 4957 after WireTransfer[bankAccount=446291, bank=OSEILLE, amount=4957] ---> WireTransfer[bankAccount=990103, bank=BLE, amount=5222] ---> WireTransfer[bankAccount=870554, bank=THUNE, amount=3776] ---> WireTransfer[bankAccount=620883, bank=BLE, amount=-5791] DESSOUSSOUS has -5392 after WireTransfer[bankAccount=636732, bank=DESSOUSSOUS, amount=-315] ---> WireTransfer[bankAccount=293064, bank=OSEILLE, amount=-570] ---> WireTransfer[bankAccount=807162, bank=THUNE, amount=-7378] BLE has -18346 after WireTransfer[bankAccount=694391, bank=BLE, amount=-9551] Rejecting the suspicious WireTransfer[bankAccount=870554, bank=THUNE, amount=3776] BLE has -13124 after WireTransfer[bankAccount=990103, bank=BLE, amount=5222] ....
Dans le main
d'une classe BankProcessor
, écrire le code décrit ci-dessus.
On veut maintenant changer le comportement du programme pour qu'une fois que 10 transferts suspicieux ont été détectés, le programme s'arrête.
Copier votre classe BankProcessor
dans une classe BankProcessorInterrupt
et dans le main
effectuer les changements nécessaires pour avoir le comportement demandé.