Contenu du cours
Multithreading en Java
Multithreading en Java
BlockingQueue et Ses Implémentations
Implémentations de base de BlockingQueue
Nous ne passerons pas en revue chaque réalisation en détail, car cela prendrait beaucoup de temps, et il est peu probable que vous en ayez besoin de toutes. Je parlerai des concepts généraux et des constructeurs qu'ils ont.
Exemple de la vie réelle
Imaginez une usine où un fil, le producteur, crée des pièces, et un autre fil, le consommateur, les traite. Le producteur place les pièces dans une file d'attente, tandis que le consommateur les récupère et les traite à partir de la file d'attente. Si la file d'attente est à court de pièces, le consommateur attend que le producteur en ajoute d'autres. Inversement, si la file d'attente est pleine, le producteur attend que le consommateur libère de l'espace.
Remarque
Un peu plus bas, nous allons implémenter cette tâche en code.
Différences par rapport aux autres types de collections
Le BlockingQueue
fournit une synchronisation automatisée, gérant l'accès des threads à la file d'attente sans nécessiter de synchronisation manuelle. Il prend également en charge les opérations de blocage pour l'ajout et la récupération d'éléments, une fonctionnalité non trouvée dans d'autres collections comme ArrayList
ou LinkedList
.
Implémentations de BlockingQueue
ArrayBlockingQueue
: Une file d'attente à taille limitée qui utilise un tableau pour stocker les éléments.
Main
// Constructor with fixed capacity BlockingQueue<String> queue1 = new ArrayBlockingQueue<>(5); // Constructor with fixed capacity and fair access BlockingQueue<String> queue2 = new ArrayBlockingQueue<>(5, true); // Constructor with fixed capacity and initial collection of elements Collection<String> initialElements = java.util.Arrays.asList("One", "Two", "Three"); BlockingQueue<String> queue3 = new ArrayBlockingQueue<>(5, false, initialElements);
Explication
Le paramètre
true
active une politique d'accès équitable en fournissant un ordre FIFO pour l'accès des threads.
LinkedBlockingQueueue
: Une file d'attente basée sur des nœuds liés qui peut être restreinte ou non restreinte.
Main
// Constructor without capacity bounds BlockingQueue<String> queue1 = new LinkedBlockingQueue<>(); // Constructor with fixed capacity BlockingQueue<String> queue2 = new LinkedBlockingQueue<>(5); // Constructor with initial collection of elements Collection<String> initialElements = java.util.Arrays.asList("One", "Two", "Three"); BlockingQueue<String> queue3 = new LinkedBlockingQueue<>(initialElements);
PriorityBlockingQueue
: Une file d'attente non bornée et priorisée où les éléments sont récupérés selon leur ordre naturel ou tel que spécifié par un comparateur.
Main
// Constructor without initial capacity (default is 11) BlockingQueue<Integer> queue1 = new PriorityBlockingQueue<>(); // Constructor with initial capacity BlockingQueue<Integer> queue2 = new PriorityBlockingQueue<>(5); // Constructor with initial capacity and comparator Comparator<Integer> comparator = Integer::compareTo; BlockingQueue<Integer> queue3 = new PriorityBlockingQueue<>(5, comparator); // Constructor with initial collection of elements Collection<Integer> initialElements = java.util.Arrays.asList(1, 3, 2); BlockingQueue<Integer> queue4 = new PriorityBlockingQueue<>(initialElements)
DelayQueue
: Une file d'attente différée où les éléments ne peuvent être récupérés qu'après l'expiration de leur délai.
DelayedElement
DelayQueueConstructors
class DelayedElement implements Delayed { private final long expirationTime; // The time when the element will be available public DelayedElement(long delay, TimeUnit unit) { this.expirationTime = System.currentTimeMillis() + unit.toMillis(delay); } @Override public long getDelay(TimeUnit unit) { long delay = expirationTime - System.currentTimeMillis(); // Calculate the remaining delay return unit.convert(delay, TimeUnit.MILLISECONDS); // Convert the delay to the specified time unit } @Override public int compareTo(Delayed o) { return Long.compare(this.expirationTime, ((DelayedElement) o).expirationTime); } }
Ce code démontre l'utilisation de la classe DelayedElement
, qui implémente l'interface Delayed
, et de la file d'attente différée DelayQueue
en Java. La classe DelayedElement
définit une méthode getDelay
pour calculer le temps de délai restant et une méthode compareTo
pour comparer les objets en fonction du temps d'expiration du délai.
La méthode main
crée deux files d'attente : queue1
, une file d'attente différée vide, et queue2
, une file d'attente initialisée avec des éléments ayant un délai de 5 et 1 seconde, respectivement.
Les éléments dans DelayQueueue
deviennent disponibles pour récupération après que le temps de délai spécifié soit écoulé.
SynchronousQueueue
: Une file d'attente sans capacité, où chaque opération d'insertion doit attendre l'opération d'extraction correspondante et vice versa.
Main
// Constructor without fair access BlockingQueue<String> queue1 = new SynchronousQueue<>(); // Constructor with fair access BlockingQueue<String> queue2 = new SynchronousQueue<>(true);
Les principales méthodes de BlockingQueue :
Ajout d'éléments :
La méthode void put(E e)
insère un élément dans la file d'attente, bloquant le thread si la file est pleine. Alternativement, la méthode boolean offer(E e, long timeout, TimeUnit unit)
tente d'ajouter un élément à la file d'attente, en attendant le temps spécifié si la file est pleine.
Main
public class BlockingQueueExample { public static void main(String[] args) { BlockingQueue<String> queue = new ArrayBlockingQueue<>(2); try { queue.put("Element 1"); // Insert the first element, no blocking. queue.put("Element 2"); // Insert the second element, no blocking. // Try to add the third element with a 2-second timeout. // Since the queue is full, it will wait for 2 seconds. boolean success = queue.offer("Element 3", 2, TimeUnit.SECONDS); } catch (InterruptedException e) { e.printStackTrace(); } } }
Cet exemple démontre l'insertion de deux éléments dans un BlockingQueue
sans blocage, suivie d'une tentative d'ajout d'un troisième élément avec un délai d'attente de 2 secondes en utilisant la méthode offer()
, qui attendra si la file d'attente est pleine.
Récupération d'Élément :
La méthode E take()
récupère et retourne un élément de la file d'attente, bloquant le thread si la file d'attente est vide. Alternativement, la méthode E poll(long timeout, TimeUnit unit)
tente de récupérer un élément de la file d'attente, en attendant le temps spécifié si la file d'attente est vide.
Main
public class BlockingQueueRetrievalExample { public static void main(String[] args) { BlockingQueue<String> queue = new ArrayBlockingQueue<>(2); try { // Adding elements to the queue queue.put("Element 1"); queue.put("Element 2"); // Retrieve and remove the first element, no blocking since the queue is not empty String item1 = queue.take(); // Returns "Element 1" // Attempt to retrieve and remove the next element with a 2-second timeout String item2 = queue.poll(2, TimeUnit.SECONDS); // Returns "Element 2" // Attempt to retrieve an element when the queue is empty, this will block for 2 seconds String item3 = queue.poll(2, TimeUnit.SECONDS); // Returns `null` after timeout } catch (InterruptedException e) { e.printStackTrace(); } } }
Ce code ajoute deux éléments à un BlockingQueue
, récupère et supprime immédiatement le premier élément, tente de récupérer l'élément suivant avec un délai d'attente de 2 secondes, et enfin essaie de récupérer un élément d'une file d'attente vide, ce qui entraîne un null
après le délai d'attente.
Vérification et Suppression des Éléments :
La méthode boolean remove(Object o)
supprime l'élément spécifié de la file d'attente s'il est présent. D'autre part, la méthode boolean contains(Object o)
vérifie si l'élément spécifié est présent dans la file d'attente sans le supprimer.
Main
public class BlockingQueueCheckRemoveExample { public static void main(String[] args) { BlockingQueue<String> queue = new ArrayBlockingQueue<>(2); try { // Adding elements to the queue queue.put("Element 1"); queue.put("Element 2"); // Check if "Element 1" is in the queue, should return `true` boolean containsElement1 = queue.contains("Element 1"); // true // Remove "Element 1" from the queue, should return `true` boolean removedElement1 = queue.remove("Element 1"); // true // Check if "Element 1" is still in the queue, should return `false` boolean containsElement1AfterRemoval = queue.contains("Element 1"); // false // Try to remove an element that is not in the queue, should return `false` boolean removedElement3 = queue.remove("Element 3"); // false } catch (InterruptedException e) { e.printStackTrace(); } } }
Ce code ajoute deux éléments à un BlockingQueue
, vérifie la présence de "Element 1", le supprime, vérifie à nouveau pour confirmer sa suppression, puis tente de supprimer un élément inexistant.
Interroge l'état de la file d'attente :
La méthode int size()
renvoie le nombre d'éléments actuellement dans la file d'attente. Pour déterminer si la file d'attente est vide, vous pouvez utiliser la méthode boolean isEmpty()
, qui vérifie si la file d'attente n'a pas d'éléments. Pour les files d'attente avec une capacité fixe, la méthode int remainingCapacity()
fournit le nombre de places restantes disponibles dans la file d'attente.
Main
public class BlockingQueueCapacityExample { public static void main(String[] args) { BlockingQueue<String> queue = new ArrayBlockingQueue<>(3); try { // Adding elements to the queue queue.put("Element 1"); queue.put("Element 2"); // Get the number of elements in the queue int currentSize = queue.size(); // 2 // Check if the queue is empty boolean isQueueEmpty = queue.isEmpty(); // false // Get the remaining capacity in the queue int remainingSpace = queue.remainingCapacity(); // 1 // Add another element to fill the queue queue.put("Element 3"); // Check the size and remaining capacity after adding the third element currentSize = queue.size(); // 3 remainingSpace = queue.remainingCapacity(); // 0 } catch (InterruptedException e) { e.printStackTrace(); } } }
Ce code ajoute des éléments à une BlockingQueue
, vérifie la taille actuelle, vérifie si la file d'attente est vide, et détermine la capacité restante, puis met à jour ces valeurs après avoir rempli complètement la file d'attente.
Réaliser un Exemple de la Vie Réelle en Code
😭 Limitations
Une limitation clé est la performance : en raison des opérations de verrouillage impliquées, la performance peut être réduite par rapport aux collections non synchronisées. De plus, les ressources peuvent devenir une préoccupation car les grandes files d'attente exigent plus de mémoire et de temps CPU pour gérer les verrous et les processus de synchronisation.
💪 Avantages
Du côté positif, le système est sécurisé en multithreading, offrant une communication sûre entre les threads sans nécessiter de gestion de synchronisation manuelle. Il simplifie également le code en évitant les constructions complexes de synchronisation et de blocage. De plus, la flexibilité des différentes implémentations de BlockingQueue
signifie qu'elles peuvent être adaptées à divers scénarios d'utilisation.
1. Qu'est-ce qu'une BlockingQueue en Java ?
2. Quelles sont les principales méthodes de BlockingQueue pour bloquer un thread ?
3. À quoi sert BlockingQueue dans les applications multithread ?
Merci pour vos commentaires !