:: Enseignements :: Master :: M1 :: 2016-2017 :: Java Avancé ::
![[LOGO]](http://igm.univ-mlv.fr/ens/resources/mlv.png) | TP noté (2) de Java Avancé |
On cherche à créer une API permettant de gérer des pools de threads.
Un pool de threads est un objet à qui on demande d'exécuter (avec la méthode execute) un code
(un Supplier) en utilisant une thread (soit en la créant soit en utilisant une thread déjà existante)
pour exécuter le code du Supplier.
La valeur de retour du Supplier n'est pas disponible directement en value de retour de execute
car on ne sait pas quand la thread aura fini d'exécuter le calcul.
On va créer une classe Result avec un champ mutable qui contiendra la valeur résultante du calcul
une fois celle-ci connu.
Pour que les choses soit un peu objet, la classe Result n'est pas visible directement mais
on manipule celle-ci à travers l'interface Promise.
Pour obtenir le résultat du calcul stocké dans un Result, l'interface Promise
permet d'enregistrer avec la méthode register des Consumer qui seront appelés
une fois le calcul effectué. Dans le cas où le calcul à déjà été effectué au moment où on appel register,
la méthode register appelera directement le Consumer.
Vos sources Java produites pendant le TP devront être placées sous le répertoire
EXAM de votre compte ($HOME) (qui est vierge dans l'environnement de TP noté);
sinon, elles ne seront pas récupérées.
Exercice 1 - Pool
Pour vous aidez, nous vous fournissons un squelette de départ de l'interface
Pool
Pool.java.
Certains tests unitaires correspondant à l'implantation sont ici:
PoolTest.java.
-
Dans un premier temps, on cherche à générifier le code de l'interface Pool.
Un Pool n'est pas générique en lui même car on peut demander d'exécuter plusieurs calculs
avec des types différents.
Par contre, le type de retour d'un calcul (donc du Supplier)
doit être le même (ou un sous-type en fait) du type que l'on va stocker dans l'objet
Result. Il faudra aussi typer correctement Promise car c'est l'interface de Result.
Attention, à mettre les wildcards correctement.
-
On souhaite maintenant implanter un premier Pool qui va créer une thread à chaque fois
que l'on demande l'exécution d'un Supplier (en utilisant la méthode execute).
La création du pool se fait avec la méthode newSimplePool.
Implanter le TODO dans newSimplePool, ajouter la gestion des Consumer
que l'on peut enregistrer sur un objet Promise (et donc sur son implantation Result).
-
si le calcul (l'appel à supplier.get()) n'est pas fini, alors on doit garder le Consumer
en mémoire jusqu'à ce que le calcul soit terminé.
-
si le calcul a déjà été effectué, on peut appeler directement la méthode accept
du Consumer avec la valeur du calcul.
-
quand le calcul se termine, il faut appeler tous les Consumer préalablement enregistrés
Le code doit être thread-safe !!
Attention: on doit pouvoir enregistrer de nouveaux Consumer alors que le calcul est entrain de s'effectuer.
-
On souhaite ajouter à l'interface Promise la méthode waitIfNotAvailableAndGet
qui comme son nom l'indique est bloquante et met en attente la thread courante tant que la valeur
du calcul correspondant à la Promise n'est pas terminé.
Comme on a déjà implanté un mécanisme permettant d'enregistrer des Consumer pour être averti
lorsque la valeur a été calculé à la question précédente, on peut utiliser ce même mécanisme
pour implanter waitIfNotAvailableAndGet.
On va donc implanter waitIfNotAvailableAndGet sous la forme d'une méthode par défaut.
L'idée est de mettre la thread courante en attente dans la méthode waitIfNotAvailableAndGet
de la classe Listener tant que la méthode accept du Listener n'a pas été appelée.
Le code doit aussi être thread-safe !!
-
On souhaite maintenant implanter un nouveau Pool de thread, ce Pool est
créé avec un nombre fixe de threads au démarrage et on utilise le design pattern producteur/comsommateur
pour transmettre les Supplier que l'on doit exécuter vers une des threads du pool de threads.
Implanter la méthode newFixedSizePool créant numberOfThreads threads et
avec une file d'attente bloquante de longeur queueSize.
Le code doit encore être thread-safe !!
Note: Pour la file d'attente vous pouvez utiliser une déjà existante dans l'API de Java où
fabriquer votre implantation.
Note 2: la valeur POISON sert pour la question suivante !
-
On cherche à ajouter une méthode close à l'interface Pool qui:
-
Après que close() est été appelée, si execute() est appelée, la méthode levera une exception IllegalStateException
empềchant d'exécuter de nouveau Supplier.
-
De plus, dans le cas d'un newFixedSizePool, lorsque close est appelée, l'ensemble des threads crées doit s'arrêter
une fois que tous les Supplier dans la file d'attente on été traités.
En terme d'implantation, on va mettre un objet POISON dans la file d'attente qui va demander aux threads de s'arrêter.
Implanter la méthode close pour les deux Pool de thread en sachant que cela doit être thread safe.
Note: il est peut-être possible d'utiliser une variable volatile ici si vous garantissez qu'il n'y aura pas de problème
de race condition.
-
Enfin, juste pour le fun, générifier les signatures puis implémenter en tant que méthodes par défaut
les méthodes filter et map de l'interface Promise
qui doivent marcher de la même façon que les méthodes filter et map sur un Stream.
Attention aux wildcards !!.
© Université de Marne-la-Vallée