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 23.
Toutes les classes demandées doivent être dans le package fr.uge.concurrence.exo1
.
Dans cet exercice, on se propose de créer une classe représentant un coffre fort un peu spécial (TreasureSafe
) dans lequel on peut stocker uniquement des trésors. Pour simplifier, on ne mettra dans le coffre que la valeur des trésors, donc des int
. À la création de coffre, on donnera la valeur maximale (la somme des valeurs des trésors) que le coffre ne doit pas dépasser. Bien entendu, le coffre fort devra être thread-safe.
La classe TreasureSafe
aura les méthodes suivantes :
putTreasure(int value)
qui essaie de rajouter un trésor de valeur value
et qui bloque si l'ajout du trésor n'est pas possible.int takeTreasure()
qui prend (donc retire) le dernier trésor ajouté et renvoie sa valeur. Si le coffre est vide, la méthode bloque jusqu'à ce qu'il y ait un trésor à enlever.
Codez la classe thread-safe TreasureSafe
décrite précédemment en utilisant des ReentrantLock
.
Pour tester votre code, on vous fournit, dans la classe TreasureSafeMain.java, un main
qui crée un coffre et démarre deux threads : un qui ajoute un trésor en boucle et l'autre qui en retire en boucle. Le programme ne doit pas s'arrêter ni se bloquer.
On veut maintenant rajouter une méthode putManyTreasures(List<Integer> values)
qui va essayer de déposer plusieurs trésors dans la coffre, dans l'ordre de la liste, tant que c'est possible. Dès que tous les trésors ont pu être ajoutés, la méthode se termine. La méthode ajoute les trésors au coffre dès qu'elle le peut et attend d'avoir épuisé la liste. On pourra ainsi déposer des trésors pour une valeur totale de 1000 dans une coffre de valeur maximale 500. Autrement dit, la méthode dépose des trésors puis, si nécessaire, attend que de la place ait été faite dans le coffre pour continuer.
On veut aussi rajouter une méthode int incomingValue()
qui renvoie la somme des valeur des trésors en attente d'être déposés par tous les threads qui sont en train d'exécuter une des méthodes putTreasure
ou putManyTreasures
.
Rajoutez les deux méthodes demandées à votre classe TreasureSafe
.
Dans le main
, on va en plus du code précédent (dé-commentez le code), démarrer un threads qui, en boucle appelle putManyTreasures
et un thread qui en boucle affiche la valeur renvoyée par incomingValue()
. Notez que pour l'instant, on ne demande à aucun thread de s'interrompre.
On veut maintenant spécifier le comportement de nos méthodes en cas d'interruption.
Si les méthodes putTreasure
et takeTreasure
sont interrompues, elles lèvent une InterruptedException
.
Si la méthode putManyTreasures
est interrompue, elle ne s'arrête pas, mais elle devient prioritaire pour déposer des trésors. Tant qu'elle n'a pas fini, aucun autre thread ne peut déposer de trésor : si un thread est en train d'appeler une des méthodes putManyTreasures
ou putTreasure
, 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 putManyTreasures
, ils sont eux aussi prioritaires et peuvent continuer à déposer des trésors. Pour signaler que le thread a été interrompu, la méthode putManyTreasures
positionnera le statut d'interruption avant de terminer.
Copiez votre classe TreasureSafe
dans une classe TreasureSafeWithInterruption
et implanter la gestion des interruptions demandée.
Pour vous aider, voici un exemple d'affichage pour la question 2 :
deposit 14 get 14 deposit 18 get 18 deposit 10 get 10 >>> incoming 0 deposit 19 get 19 deposit 1 get 1 >>> incoming 0 Thread-1 tries to deposit [100, 50, 200, 75, 100, 25, 50, 30] deposit 13 get 100 get 50 Thread-1 out get 200 deposit 10 deposit 11 >>> incoming 0 get 75 get 13 deposit 18 get 100 deposit 10 get 25 deposit 4 >>> incoming 0 get 50 deposit 4 Thread-1 tries to deposit [100, 50, 200, 75, 100, 25, 50, 30] get 30 deposit 6 get 10 deposit 6 get 11 >>> incoming 205 deposit 8 get 18 deposit 4 get 10 deposit 3 get 4 deposit 20 >>> incoming 205 get 4 deposit 6 deposit 12 get 100 deposit 6 get 50 deposit 15 >>> incoming 80 get 200 Thread-1 out get 75 deposit 1 deposit 1 get 6 deposit 2 >>> incoming 0 get 6 deposit 13 get 8 deposit 19 get 4 deposit 19 >>> incoming 0 get 3 Thread-1 tries to deposit [100, 50, 200, 75, 100, 25, 50, 30] deposit 11 deposit 7 get 20 deposit 9 get 6 deposit 2 >>> incoming 480 get 12 deposit 2 get 100 deposit 9 get 6 deposit 6 get 25 >>> incoming 480 deposit 9 get 15 deposit 1 get 50 get 30 deposit 16 get 1 deposit 6 >>> incoming 280 get 1 deposit 6 deposit 3 get 2 deposit 6 get 13 deposit 17 get 19 >>> incoming 280 deposit 19 get 19 deposit 3 get 100 deposit 18 >>> incoming 205 get 50 deposit 8 get 11 deposit 1 get 7 deposit 15 get 9 >>> incoming 205 get 2 deposit 8 get 2 deposit 16 get 9 deposit 8 >>> incoming 205 get 6 deposit 4 get 9 deposit 6 get 1 >>> incoming 205 deposit 9 get 200 Thread-1 out deposit 20 get 16 deposit 3 get 6 deposit 4 >>> incoming 0 get 6 deposit 3 get 3 deposit 7 get 6 get 17 deposit 15 >>> incoming 0 Thread-1 tries to deposit [100, 50, 200, 75, 100, 25, 50, 30] deposit 12 get 19 deposit 15 get 3 deposit 11 >>> incoming 630 deposit 14 get 75 get 18 get 8 deposit 18 get 1 >>> incoming 543 get 15 deposit 13 get 8 get 16 deposit 19 >>> incoming 530 get 8 deposit 13 get 4 deposit 10 get 6 deposit 9 >>> incoming 530 get 9 get 100 deposit 17 deposit 15 get 25 >>> incoming 480 deposit 14 get 50 deposit 13 get 30 deposit 11 get 20 deposit 6 >>> incoming 480 ...
Toutes les classes demandées doivent être dans le package fr.uge.concurrence.exo2
.
Dans cet exercice, nous allons utiliser une variante de l'API fictive de requêtes
Request.java vue en TP. Cette API très simplifiée permet de demander le prix d'un objet sur
un site de vente en ligne. Pour faire cette demande,
on crée une Request
avec le nom de l'objet
et le nom du site. La méthode request(int timeoutMilli)
effectue la demande et renvoie la réponse
sous la forme d'un Optional
de Answer.java
. La méthode bloque en attendant la réponse mais cette attente est bornée à au plus timeoutMilli
millisecondes.
Request request = new Request("amazon.fr", "pikachu"); Optional<Answer> answer = request.request(5_000); if (answer.isPresent()) { System.out.println("The price is " + answer.orElseThrow().price()); } else { System.out.println("The price could not be retrieved from the site"); }
La réponse renvoyée par request()
n'est pas nécessairement fructueuse, c'est pourquoi elle renvoie un Optional<Answer>
. Si le site ne dispose pas de l'item, un Optional
vide est renvoyé. Sinon, une réponse est fournie et on peut récupérer le prix avec la méthode price()
de l'Answer
. De plus, et c'est là, la différence avec l'API d'origine, si le site ne répond pas avant le timeout donné en paramètre, une SocketTimeoutException
est levée, avec comme message le nom du site qui a provoqué le timeout.
La liste (List<String>
) de tous les sites connus par l'API Request
peut-être obtenue par
Request.getAllSites()
.
Dans un premier temps, l'objectif est de trouver, étant donnée une liste d'items, le site qui donne un prix pour l'ensemble des items le plus rapidement possible. Pour cela, on va utiliser un nombre fixe de threads pour chaque site, qui vont en boucle demander les prix de différents items, de façon à couvrir toute la liste.
Attention, pour simplifier l'exercice, on ne vous demande pas de gérer le cas où aucun site ne possède tous les items.
En utilisant le design pattern producteur-consommateur, compléter la classe FastestWithAllItems.java pour que la méthode retrieve
demande les prix de tous les items de la liste passée en paramètre sur tous les sites et renvoie la liste des Answer
du premier site pour lequel on a obtenu tous les prix.
Pour chaque site, la méthode doit utiliser threadsPerSite
threads pour demander les prix des items. Bien sûr, il faut faire en sorte de ne pas demander plusieurs fois le prix d'un item sur un site donné. Et c'est le thread main
qui s'occupera de renvoyer la première réponse complète.
Pour l'instant, si une requête échoue parce que le timeout est dépassé, on la traite comme si l'item n'était pas disponible sur le site.
Tous les threads démarrés doivent être interrompus à la sortie de retrieve
.
Attention, ici, vous devez utiliser le pattern producteur-consommateur et toute autre forme de synchronisation sera considérée comme hors-sujet. Vous n'avez pas non plus le droit d'utiliser un ExecutorService
pour cette question et la suivante.
Recopier votre code de la question 1 dans un classe FastestWithAllItemsWithInterrupt.java
et modifier la méthode retrieve
pour que si une requête échoue sur un site parce que le timeout est dépassé, tous les threads qui effectuent des requêtes pour le même site soient également interrompus (en affichant un message à ce moment là).
Bonus : faire également en sorte que si un item n'est pas présent sur un site, tous les threads qui effectuent des requêtes pour le même site soient interrompus.
Voici le genre d'affichage que vous devriez obtenir pour la liste List.of("tortank", "pikachu", "evoli", "miaouss", "salameche")
:
DEBUG : starting request for pikachu on patissier.fr DEBUG : starting request for pikachu on leboncorner.fr DEBUG : starting request for tortank on princedesvoleurs.com DEBUG : starting request for tortank on ledoute.fr DEBUG : starting request for pikachu on shady.com DEBUG : starting request for tortank on tardy.fr DEBUG : starting request for tortank on omazen.uk ... DEBUG : starting request for miaouss on ledoute.fr DEBUG : pikachu costs 426 on princedesvoleurs.com DEBUG : pikachu costs 876 on les3alpes.fr DEBUG : starting request for evoli on les3alpes.fr DEBUG : starting request for evoli on princedesvoleurs.com DEBUG : pikachu costs 830 on patissier.fr DEBUG : starting request for miaouss on patissier.fr DEBUG : tortank costs 218 on shady.com DEBUG : starting request for evoli on shady.com DEBUG : tortank costs 338 on omazen.uk DEBUG : starting request for evoli on omazen.uk DEBUG : pikachu costs 266 on shady.com ... DEBUG : evoli costs 913 on fmac.fr DEBUG : starting request for salameche on fmac.fr DEBUG : salameche is not available on tombeducamion.fr DEBUG : Request evoli@les3alpes.fr has timeout Thread-18 interrupted for site les3alpes.fr Thread-19 interrupted for site les3alpes.fr DEBUG : miaouss costs 431 on leboncorner.fr DEBUG : starting request for salameche on leboncorner.fr DEBUG : Request miaouss@patissier.fr has timeout Thread-4 interrupted for site patissier.fr Thread-5 interrupted for site patissier.fr DEBUG : salameche costs 246 on shady.com DEBUG : evoli costs 111 on leboncorner.fr DEBUG : salameche costs 992 on ledoute.fr DEBUG : miaouss costs 521 on fmac.fr DEBUG : salameche costs 407 on bdiscount.fr [tortank@bdiscount.fr : 491, pikachu@bdiscount.fr : 811, miaouss@bdiscount.fr : 73, evoli@bdiscount.fr : 345, salameche@bdiscount.fr : 407]
En utilisant un ExecutorService
, compléter la classe CheapestWithAllItems.java pour que la méthode retrieve
demande les prix de tous les items de la liste passée en paramètre sur tous les sites et renvoie la liste des Answer
d'un site pour lequel on a obtenu tous les prix pour une somme totale minimale.
La méthode doit créer une tâche par site et par item pour demander les prix et elle seront effectuées par poolSize
threads au total. Votre code devra en plus garantir
que l'exécution de retrieve
ne prend pas plus de timeoutMilliGlobal
millisecondes (pour rappel, un ExecutorService
possède des méthodes d'invocation de tâches avec timeout).
Si une requête échoue parce que le timeout est dépassé, le thread doit l'ignorer en affichant que le site n'as pas répondu à temps.
Aide : si answerLists
est un ensemble de listes d'Answer
, le code suivant donne la liste de taille nbItems
de prix minimum :
answerLists.stream().filter(set -> set.size() == nbItems) .min(Comparator.comparingInt(set -> set.stream().mapToInt(Answer::price).sum())) .orElse(List.<Answer>of());
Voici le genre d'affichage que vous devriez obtenir pour la liste List.of("tortank", "pikachu", "evoli")
:
DEBUG : starting request for pikachu on patissier.fr DEBUG : starting request for evoli on patissier.fr DEBUG : starting request for evoli on fmac.fr DEBUG : starting request for tortank on tardy.fr DEBUG : starting request for pikachu on fmac.fr DEBUG : starting request for tortank on fmac.fr DEBUG : starting request for tortank on bdiscount.fr DEBUG : starting request for tortank on patissier.fr DEBUG : starting request for evoli on tardy.fr DEBUG : starting request for pikachu on tardy.fr DEBUG : tortank is not available on patissier.fr DEBUG : starting request for pikachu on bdiscount.fr DEBUG : pikachu costs 830 on patissier.fr DEBUG : starting request for evoli on bdiscount.fr DEBUG : evoli costs 913 on fmac.fr DEBUG : starting request for tortank on tombeducamion.fr ... DEBUG : evoli costs 111 on leboncorner.fr DEBUG : pikachu costs 266 on shady.com DEBUG : pikachu costs 964 on ledoute.fr DEBUG : Request evoli@les3alpes.fr has timeout DEBUG : evoli costs 716 on shady.com tombeducamion.fr did not respond in time omazen.uk did not respond in time les3alpes.fr did not respond in time [tortank@tardy.fr : 372, pikachu@tardy.fr : 36, evoli@tardy.fr : 170]