On reprend l'exercice du TP sur les signaux où l'on veut créer une file d'attente thread-safe.
Dans un premier temps, vous avez réalisé une file
d'attente thread-safe non-bornée appelée UnboundedSafeQueue<V>
qui offrait deux méthodes:
add(V value)
qui ajoute un élément à la fin de la file,V take()
qui retire le premier élément de la file et le renvoie. Si la file est vide, la méthode doit attendre jusqu'à ce qu'un élément soit ajouté..Vous avez dû obtenir du code qui ressemble à :
public class UnboundedSafeQueue<V> { private final ArrayDeque<V> fifo = new ArrayDeque<>(); private final Object lock = new Object(); public void add(V value) { synchronized (lock) { fifo.add(value); lock.notify(); } } public V take() throws InterruptedException { synchronized (lock) { while (fifo.isEmpty()) { lock.wait(); } return fifo.remove(); } } }
Ré-écrire (refactor) cette classe pour qu'elle utilise l'API des ReentrantLock
.
Penser à tester que tout s'affiche correctement.
Ensuite, vous avez réalisé une file d'attente thread-safe de taille bornée BoundedSafeQueue<V>
. La capacité maximale est fixée à la création de la file et la taille de la file ne devra jamais dépasser cette capacité. La méthode add
n'est donc plus adaptée car elle ne prend pas en compte la capacité maximale. On la remplace par une méthode put(V value)
qui ajoute value
s'il y a la place et sinon attend jusqu'à ce qu'une place se libère.
Vous avez dû obtenir du code qui ressemble à :
public class BoundedSafeQueue<V> { private final ArrayDeque<V> fifo = new ArrayDeque<>(); private final int capacity; private final Object lock = new Object(); public BoundedSafeQueue(int capacity) { if (capacity <= 0) { throw new IllegalArgumentException(); } this.capacity = capacity; } public void add(V value) throws InterruptedException { synchronized (lock) { while (fifo.size() == capacity) { lock.wait(); } fifo.add(value); lock.notifyAll(); } } public V take() throws InterruptedException { synchronized (lock) { while (fifo.isEmpty()) { lock.wait(); } lock.notifyAll(); return fifo.remove(); } }
Pourquoi utilise-t-on notifyAll
et non pas notify
ici ?
Récrire (refactor) cette classe pour qu'elle utilise l'API des ReentrantLock
L'API des ReentrantLock
offre un moyen d'éviter d'utiliser signalAll
dans ce contexte. Modifier le code en conséquence.
Vérifier que l'on observe le comportement attendu quand on a plus de 5 threads (10, 50, ...) qui utilisent la méthode add
.
On souhaite écrire une classe thread-safe Sync
qui possède deux méthodes safe
et inSafe
. La méthode safe
garantit qu'un seul thread à la fois peut exécuter le code d'un Supplier
(et ce, quel que soit le Supplier
). La méthode inSafe
,
renvoie vrai si un thread (n'importe lequel) est en train d'exécuter un Supplier
avec la méthode safe
en même temps.
public class Sync<V> { public boolean inSafe() { // TODO } public V safe(Supplier<? extends V> supplier) throws InterruptedException { return supplier.get(); // TODO } }
Écrire le code d'une classe Counter
ayant une méthode count
qui renvoie une valeur et incrémente celle-ci à chaque appel.
Afin d'être thread-safe, votre implantation devra utiliser la classe Sync
et supposant qu'il existe déjà une implantation.
La classe Counter
devra aussi avoir une méthode isSomeOneCounting
qui renvoie vrai si un thread est en train d’utiliser sa méthode count()
.
Écrire également le code du main
de la classe Counter
qui créé un compteur et deux threads qui affichent chacun les valeurs renvoyées par le compteur dans une boucle infinie. Ajouter un thread qui affiche à intervalles réguliers si un autre thread est en train de compter ou pas.
Écrire le code de la classe Sync
en utilisant des blocs synchronized
.
Écrire le code de la classe Sync
en utilisant les verrous du package java.util.concurrent
.
On souhaite maintenant écrire une classe thread-safe PermitSync
qui possède une méthode safe
.
La méthode safe
garantit qu'un nombre de threads inférieur ou égal à permits
peuvent exécuter en même temps le code d'un Supplier
de leur choix (et ce, quels que soient les Supplier
choisis).
public class PermitSync<V> { public PermitSync(int permits) { // TODO } public V safe(Supplier<? extends V> supplier) { return supplier.get(); // TODO } }
Écrire le code de la classe PermitSync
en utilisant des blocs synchronized
.
Écrire le code de la classe PermitSync
en utilisant les verrous du package
java.util.concurrent
.
Dans cet exercice, on veut écrire un programme qui lance 5 threads qui cherchent en boucle des grands nombres premiers. Le thread main attend que 10 nombres premiers aient été trouvés, affiche leur somme et s'arrête.
Le code des threads ressemblera au code suivant :
for (;;) { long nb = 1_000_000_000L + ThreadLocalRandom.current().nextLong(1_000_000_000L); if (isPrime(nb)) { ... } }
avec
public static boolean isPrime(long l) { if (l <= 1) { return false; } for (long i = 2L; i <= l / 2; i++) { if (l % i == 0) { return false; } } return true; }
On souhaite réaliser cette tâche avec les contraintes suivantes :
MyThreadSafeClass
respectant toutes les bonnes pratiques vues en cours et qui va permettre aux threads de communiquer avec le thread main.
MyThreadSafeClass
) dans le code de la méthode main
.
Décrire le contrat (c'est à dire quelles sont les méthodes qu'elle doit fournir et ce qu'elles font précisément) d'une classe MyThreadSafeClass
permettant de réaliser cette tâche.
Écrire la classe MyThreadSafeClass
, en utlisant l'API des ReentrantLock
.
Écrire la méthode main
d'une classe SumFirstTenPrimes
qui réalise la tâche demandée en utilisant MyThreadSafeClass
.
Dans cet exercice, on cherche à écrire une classe thread-safe Semaphore
. Un objet de cette classe (un sémaphore) contient un certain nombre de permis d’exécution (le nombre initial est fixé à la construction) et permet d'en acquérir et d'en rendre.
La classe ne stocke pas réellement des permis (il n'y a pas d'objet "permis") mais simplement le nombre de permis disponibles dans le sémaphore.
Le nombre initial de permis est passé en argument du constructeur de la classe. La classe fournit 3 méthodes :
void release()
remet un permis dans le sémaphore (il est possible dépasser le nombre initial de permis).boolean tryAcquire()
qui retire un permis du sémaphore s'il y en a au moins un de disponible. Dans ce cas, la méthode renvoie true
. S'il n'y a pas de permis disponible, elle renvoie false
.
void acquire()
qui prend un permis s'il y en a au moins un de disponible ou qui bloque jusqu'à ce qu'il y en ait un et le prend.En utilisant l'API des ReentrantLock
, écrire une classe Semaphore
thread-safe et qui respecte le contrat ci-dessus.
Remarque : ce n'est pas une bonne idée, en termes de lisibilité, d'utliser tryAquire
dans le code de la méthode aquire
.
Rajouter à votre classe Semaphore
une méthode main
qui crée un sémaphore contenant 5 permis puis démarre 10 threads. Chaque thread attend de pouvoir prendre un permis. Puis, après l'avoir pris, il attend 1 seconde et rend le permis. Chaque thread affichera un message (avec son numéro) après avoir acquis et après avoir rendu un permis.
On veut étendre les fonctionnalités de notre classe en rajoutant la possibilité de savoir combien de threads sont bloqués en attente d'un permis d'exécution. Pour cela, on va rajouter une méthode int waitingForPermits()
qui renvoie le nombre de threads qui sont en attente dans acquire
.
Recopier votre classe Semaphore
dans une classe SemaphoreClosable
et rajouter la méthode
waitingForPermits
décrite ci-dessus. Modifier le code pour que le main
attende 1 seconde après le démarrage des threads et affiche la valeur renvoyée par waitingForPermits
.
On veut maintenant ajouter la possibilité de fermer le sémaphore. Pour cela on va rajouter une méthode void close()
. La fermeture du sémaphore devra avoir le comportement suivant :
acquire
et tryAcquire
lèvent une IllegalStateException
,
acquire
lèvent une AsynchronousCloseException
,
release
n'ont aucun effet.
Rajouter la méthode close
décrite ci-dessus à votre classe SemaphoreClosable
.
On veut modifier le main
pour qu'après avoir démarré les threads, il attende 10 secondes et ferme le sémaphore s'il n'y a aucun thread en attente dans un acquire
. Quelqu'un propose le code ci-dessous :
var semaphore = new Semaphore(...); ... if (semaphore.waitingForPermits() == 0){ semaphore.close(); }
Quel est le problème avec le code proposé ? Comment faire évoluer la classe SemaphoreClosable
pour pouvoir obtenir cette fonctionnalité ? Répondez en commentaire dans la classe SemaphoreClosable
.