Vous êtes sur la page 1sur 8

12/12/2023, 11:18 7.

Files d'attente

7. Files d'attente

7.1. Introduction, pattern producteur / consommateur

Il est difficile de parler des files d'attente en programmation concurrente sans parler du pattern producteur /
consommateur. Ce pattern est très simple à comprendre, et son utilisation est très répandue dans la pratique. Java A P I avancées
On définit trois éléments :
Cour s & T utoriaux
un producteur, qui produit des tâches à effectuer ;
Reto u r a u blog Java le soir
un consommateur, qui exécute les tâches une par une ;
une file d'attente, qui permet au producteur d'enregistrer ses tâches, et au consommateur d'en retirer. Somm a i r e
P. p r é c é d ente P. s u i v a n te
Si le producteur produit trop de tâches par rapport à la capacité de consommation du consommateur, alors la
file d'attente risque de saturer, et le système ne fonctionnera pas correctement. Au contraire, si le Table des matières
consommateur exécute ses tâches trop vite, alors la file d'attente finira par s'assécher. Ce comportement n'est
API Collection
pas problématique. L'avantage de ce pattern est sa souplesse. Le producteur et le consommateur ne se
connaissent pas, et ne communiquent pas entre eux directement. Il n'y a donc pas de problème de 1. Introduction
concurrence d'accès entre eux. On peut étendre facilement ce pattern, au cas où l'on aurait plusieurs 2. Interface Collection
consommateurs, et plusieurs producteurs. Le système garde sa souplesse, puisque s'il manque des 2.1. Notion de Collection
consommateurs, on peut toujours en ajouter. Le point délicat d'un tel système est la file d'attente qui permet 2.2. Détail des méthodes disponibles
aux tâches de passer d'un producteur à un consommateur. Cette file d'attente est utilisée en forte 2.3. Interface Iterator
concurrence d'accès, et ne doit pas constituer un goulet d'étranglement dans notre système. L'API Concurrent 2.4. Implémentation, exemples
nous fournit une interface modélisant exactement cette file d'attente : BlockingQueue<E>. d'utilisation

3. Interface List
7.2. Interface BlockingQueue<E> 3.1. Notion de List
3.2. Détail des méthodes disponibles
Cette interface est une extension de l'interface Queue<E> que nous avons déjà vue dans la partie sur l'API 3.3. Interface ListIterator
Collection. Rappelons que l'interface Queue<E> permet de modéliser une file d'attente, de taille éventuellement 3.4. Implémentations, exemples
fixée, et qui ne peut pas être modifiée. D'une certaine façon, une file d'attente supporte trois opérations de d'utilisation
base :
4. Interface Set
l'ajout d'un élément ; 4.1. Notion de Set
le retrait d'un élément disponible ; 4.2. Implémentations HashSet et
l'examen du prochain élément disponible, sans le retirer. LinkedHashSet
4 3 Exemples d'utilisation
https://blog.paumard.org/cours/java-api/chap05-concurrent-queues.html 1/8
12/12/2023, 11:18 7. Files d'attente
4.3. Exemples d utilisation
Comme ces trois opérations peuvent échouer (cas d'une file d'attente vide, ou au contraire saturée), l'interface
Queue<E> définit deux comportement d'échec pour chacune de ces trois opérations. Ces deux comportements 5. Interface SortedSet
sont la génération d'une exception ou le renvoi d'un booléen à false. L'interface BlockingQueue définit deux 5.1. Notion de SortedSet
comportements supplémentaires.
5 2 Dét il d éth d
La demande d'ajout par la méthode put(E e), ou de retrait d'un élément par la méthode take() peut
bloquer si la file d'attente est saturée, ou si elle est vide. Ces deux méthodes attendent en fait la fin de
la saturation, ou l'arrivée d'un nouvel élément pour rendre la main.
Il existe deux autres méthodes, offer(E e, long time, TimeUnit unit) et poll(long time, TimeUnit
unit) qui ont le même fonctionnement, sauf qu'elles ne peuvent attendre plus longtemps que le temps
passé en paramètre. Quand ce temps est écoulé, elles retournent false ou null respectivement.
Java A P I avancées
L'interface BlockingQueue expose également les méthodes drainTo(Collection coll) et drainTo(Collection
coll, int maxElements), qui permettent de retirer des éléments par paquets, pour les stocker dans la Cour s & T utoriaux
collection passée en paramètre.
Reto u r a u blog Java le soir

Somm a i r e
7.3. Implémentations de BlockingQueue
P. p r é c é d ente P. s u i v a n te

L'API Concurrent propose plusieurs implémentations de BlockingQueue. Les deux premières sont Table des matières
ArrayBlockingQueue et LinkedBlockingQueue. Ces deux files d'attente fonctionnent sur le principe FIFO.
API Collection
L'implémentation ArrayBlockingQueue a une capacité maximale, qui est fixée à la construction de l'objet.
L'implémentation LinkedBlockingQueue peut être limitée en capacité lors de sa construction. Si aucune limite 1. Introduction
n'est fixée, alors elle utilise sa valeur par défaut : Integer.MAX_VALUE. L'implémentation 2. Interface Collection
PriorityBlockingQueue conserve ses éléments ordonnés, soit en utilisant un comparateur (instance de 2.1. Notion de Collection
Comparator) passé lors de sa construction, soit en utilisant l'ordre de ses éléments si ce sont des 2.2. Détail des méthodes disponibles
implémentations de Comparable. On peut lui donner une taille maximale, ou la laisser à sa valeur par défaut, 2.3. Interface Iterator
Integer.MAX_VALUE. Enfin, l'implémentation SynchronousQueue a un comportement un peu particulier. En fait 2.4. Implémentation, exemples
elle n'a de file d'attente que le nom, puisque sa capacité est de 0. On ne peut donc pas l'interroger sur son d'utilisation
contenu, ni itérer sur ses éléments, tout simplement parce qu'elle n'en possède pas. En fait, chaque insertion
3. Interface List
d'une nouvelle donnée dans une telle file, doit attendre qu'une demande de retrait soit faite pour rendre la
3.1. Notion de List
main. Réciproquement, chaque demande de retrait ne rend la main que lorsqu'une demande d'insertion est
3.2. Détail des méthodes disponibles
faite. Cette classe permet à deux threads de se synchroniser sur des points de rendez-vous particuliers.
3.3. Interface ListIterator
3.4. Implémentations, exemples
7.4. Exemple de producteur / consommateur d'utilisation

4. Interface Set
Construire un tel exemple n'est pas très compliqué, mais cela va nous donner l'occasion de montrer comment
4.1. Notion de Set
arrêter un tel système proprement. Notre système est une boulangerie. Cette boulangerie est alimentée
4.2. Implémentations HashSet et
régulièrement en pain par des boulangers, et de temps en temps des clients affamés se présentent. Voyons le
LinkedHashSet
code.
4 3 Exemples d'utilisation
https://blog.paumard.org/cours/java-api/chap05-concurrent-queues.html 2/8
12/12/2023, 11:18 7. Files d'attente
4.3. Exemples d utilisation
Exemple 74. Producteur / consommateur : la boulangerie
5. Interface SortedSet
public class Boulangerie {
5.1. Notion de SortedSet
5 2 Dét il d éth d
// plus prosaïquement, une boulangerie est une file d'attente de 20 cases
private BlockingQueue<Pain> queue = new ArrayBlockingQueue<Pain>(20) ;

// on peut y déposer du pain, mais le boulanger n'est pas patient


// si le panier de vente est plein, il s'en va
public boolean depose(Pain pain) throws InterruptedException {
return queue.offer(pain, 200, TimeUnit.MILLISECONDS) ;
Java A P I avancées
}
Cour s & T utoriaux
// on peut en acheter, et le client n'est pas plus patient
// que le boulanger Reto u r a u blog Java le soir
public Pain achete () throws InterruptedException {
Somm a i r e
return queue.poll(200, TimeUnit.MILLISECONDS) ;
} P. p r é c é d ente P. s u i v a n te

Table des matières


// on peut interroger le stock
public int getStock() { API Collection
return queue.size() ; 1. Introduction
} 2. Interface Collection
} 2.1. Notion de Collection
2.2. Détail des méthodes disponibles
2.3. Interface Iterator
La classe Pain est juste une classe standard, sans propriété particulière. Nous avons ensuite besoin de deux 2.4. Implémentation, exemples
classes : Boulanger et Mangeur, toutes deux implémentations de Runnable. d'utilisation

Exemple 75. Producteur / consommateur : la classe Boulanger 3. Interface List


3.1. Notion de List
public class Boulanger implements Runnable { 3.2. Détail des méthodes disponibles
3.3. Interface ListIterator
public void run() { 3.4. Implémentations, exemples
d'utilisation
try {
while (true) { 4. Interface Set
4.1. Notion de Set
// toutes les secondes un boulanger produit un pain 4.2. Implémentations HashSet et
Thread.sleep(1000) ; LinkedHashSet
4 3 Exemples d'utilisation
https://blog.paumard.org/cours/java-api/chap05-concurrent-queues.html 3/8
12/12/2023, 11:18 7. Files d'attente
4.3. Exemples d utilisation
boolean added = boulangerie.depose(new Pain()) ;
5. Interface SortedSet
if (added) {
5.1. Notion de SortedSet
System.out.println("[" + Thread.currentThread().getName() + "]" + 5 2 Dét il d éth d
"[" + boulangerie.getStock() + "] je livre.") ;
} else {
System.out.println("[" + Thread.currentThread().getName() + "]" +
"[" + boulangerie.getStock() + "] la boulangerie est pleine.") ;
}
}

Java A P I avancées
} catch (InterruptedException e) {
System.out.println("[" + Thread.currentThread().getName() + "] je m'arrête") ; Cour s & T utoriaux
}
} Reto u r a u blog Java le soir
}
Somm a i r e
P. p r é c é d ente P. s u i v a n te

Exemple 76. Producteur / consommateur : la classe Mangeur Table des matières

public class Mangeur implements Runnable { API Collection


1. Introduction
public void run() { 2. Interface Collection
2.1. Notion de Collection
try { 2.2. Détail des méthodes disponibles
2.3. Interface Iterator
while (true) { 2.4. Implémentation, exemples
// nos mangeurs mangent de façon aléatoire... d'utilisation
Thread.sleep(rand.nextInt(1000)) ;
3. Interface List
Pain pain = boulangerie.achete() ;
if (pain != null) { 3.1. Notion de List
System.out.println("[" + Thread.currentThread().getName() + "]" + 3.2. Détail des méthodes disponibles
"[" + boulangerie.getStock() + "] miam miam") ; 3.3. Interface ListIterator
} else { 3.4. Implémentations, exemples
System.out.println("[" + Thread.currentThread().getName() + "]" + d'utilisation
"[" + boulangerie.getStock() + "] j'ai faim") ;
4. Interface Set
}
4.1. Notion de Set
}
4.2. Implémentations HashSet et
LinkedHashSet
4 3 Exemples d'utilisation
https://blog.paumard.org/cours/java-api/chap05-concurrent-queues.html 4/8
12/12/2023, 11:18 7. Files d'attente
4.3. Exemples d utilisation
} catch (InterruptedException e) {
System.out.println("[" + Thread.currentThread().getName() + "] je m'arrête") ; 5. Interface SortedSet
} 5.1. Notion de SortedSet
} 5 2 Dét il d éth d
}

Il ne nous reste plus qu'à faire fonctionner notre système, dans la méthode main() suivante.

Exemple 77. Producteur / consommateur : le système

public static void main(String[] args) {


Java A P I avancées

// on initialise une boulangerie, et une variable aléatoire pour nos client Cour s & T utoriaux
final Boulangerie boulangerie = new Boulangerie() ;
final Random rand = new Random() ; Reto u r a u blog Java le soir

Somm a i r e
// notre boulanger est un runnable
P. p r é c é d ente P. s u i v a n te
Boulanger boulanger = new Boulanger() ;
Table des matières
// notre mangeur est aussi un runnable
Mangeur mangeur = new Mangeur() ; API Collection
1. Introduction
Thread [] boulangers = new Thread[5] ; 2. Interface Collection
Thread [] mangeurs = new Thread[2] ; 2.1. Notion de Collection
2.2. Détail des méthodes disponibles
// préparation des boulangers 2.3. Interface Iterator
for (int i = 0 ; i < boulangers.length ; i++) { 2.4. Implémentation, exemples
boulangers[i] = new Thread(boulanger) ; d'utilisation
}
3. Interface List

// préparation des mangeurs 3.1. Notion de List


for (int i = 0 ; i < mangeurs.length ; i++) { 3.2. Détail des méthodes disponibles
mangeurs[i] = new Thread(mangeur) ; 3.3. Interface ListIterator
} 3.4. Implémentations, exemples
d'utilisation
// lancement des boulangers
4. Interface Set
for (int i = 0 ; i < boulangers.length ; i++) {
4.1. Notion de Set
boulangers[i].start() ;
4.2. Implémentations HashSet et
}
LinkedHashSet
4 3 Exemples d'utilisation
https://blog.paumard.org/cours/java-api/chap05-concurrent-queues.html 5/8
12/12/2023, 11:18 7. Files d'attente
4.3. Exemples d utilisation

// lancement des mangeurs 5. Interface SortedSet


for (int i = 0 ; i < mangeurs.length ; i++) {
5.1. Notion de SortedSet
mangeurs[i].start() ; 5 2 Dét il d éth d
}
}

On peut faire tourner ce système, et il peut fonctionner correctement. On peut ajuster le nombre de
boulangers et de clients, sans avoir à changer le code ni du boulanger, ni du client. S'il y a trop de boulangers,
la file d'attente saturera, et s'il y a trop de clients, elle s'assèchera, ce qui est, dans les deux cas, attendu.
Dans une application réelle, le boulanger et le client seraient dans deux classes séparées, sans contact l'une Java A P I avancées
avec l'autre.
Cour s & T utoriaux

7.5. Arrêter un producteur / consommateur : pilule empoisonnée Reto u r a u blog Java le soir

Somm a i r e
La question qui se pose est la suivante : comment arrêter un tel système proprement, en garantissant que
P. p r é c é d ente P. s u i v a n te
tous les éléments placés dans la file d'attente sont bien traités par les consommateurs ? On peut commencer
par interrompre les producteurs, ce qui ne pose pas de problème. La file d'attente finira par s'assécher, et l'on Table des matières
pourra interrompre ensuite les consommateurs. Cette approche fonctionne, mais impose un temps d'attente
API Collection
qui peut poser problème. Si l'on interrompt de la même manière les consommateurs, il y a toutes les chances
pour que des éléments de la file d'attente ne soient pas traités. Comment arrêter les producteurs et les 1. Introduction
consommateurs le plus rapidement possible, tout en garantissant que tous les éléments de la file d'attente 2. Interface Collection
ont bien été traités ? Le pattern utilisé pour ce faire porte le nom de poison pill : pilule empoisonnée. On place 2.1. Notion de Collection
dans la file d'attente des éléments particuliers, autant que de consommateurs, qui vont tuer chaque 2.2. Détail des méthodes disponibles
consommateur, un par un. Quand un consommateur consomme une de ces pilules empoisonnée, il s'arrête de 2.3. Interface Iterator
fonctionner, et notamment, il ne prend plus d'élément dans la file d'attente. Pour cela, on peut ajouter une 2.4. Implémentation, exemples
constante à notre classe Pain. d'utilisation

Exemple 78. Pilule empoisonnée : création de la pilule 3. Interface List


3.1. Notion de List
public class Pain { 3.2. Détail des méthodes disponibles
3.3. Interface ListIterator
// pain empoisonné ! 3.4. Implémentations, exemples
public static final PAIN_EMPOISONNE = new Pain() ; d'utilisation
}
4. Interface Set
4.1. Notion de Set
Lorsqu'un consommateur mange un tel pain, il doit mourir, et donc arrêter de fonctionner. Le code de notre 4.2. Implémentations HashSet et
consommateur devient donc le suivant. LinkedHashSet
4 3 Exemples d'utilisation
https://blog.paumard.org/cours/java-api/chap05-concurrent-queues.html 6/8
12/12/2023, 11:18 7. Files d'attente
4.3. Exemples d utilisation
Exemple 79. Pilule empoisonnée : modification du consommateur
5. Interface SortedSet
// notre mangeur devient empoisonnable !
5.1. Notion de SortedSet
Runnable mangeur = new Runnable() {
5 2 Dét il d éth d

public void run() {

try {

while (true) {
// nos mangeurs mangent de façon aléatoire...
Java A P I avancées
Thread.sleep(rand.nextInt(1000)) ;
Pain pain = boulangerie.achete() ; Cour s & T utoriaux
if (pain != null) {
Reto u r a u blog Java le soir
if (pain == Pain.PAIN_EMPOISONNE) {
Somm a i r e
System.out.println("Je meurs !") ;
Thread.currentThread().interrupt() ; P. p r é c é d ente P. s u i v a n te

} else { Table des matières


System.out.println("[" + Thread.currentThread().getName() + "]" +
"[" + boulangerie.getStock() + "] miam miam") ; API Collection
} 1. Introduction
2. Interface Collection
} else { 2.1. Notion de Collection
System.out.println("[" + Thread.currentThread().getName() + "]" + 2.2. Détail des méthodes disponibles
"[" + boulangerie.getStock() + "] j'ai faim") ; 2.3. Interface Iterator
} 2.4. Implémentation, exemples
} d'utilisation

3. Interface List
} catch (InterruptedException e) {
} 3.1. Notion de List
} 3.2. Détail des méthodes disponibles
}; 3.3. Interface ListIterator
3.4. Implémentations, exemples
d'utilisation
Enfin, le code d'arrêt de notre système est le suivant, à ajouter à la fin de la méthode main().
4. Interface Set
Exemple 80. Pilule empoisonnée : code d'arrêt du système 4.1. Notion de Set
4.2. Implémentations HashSet et
LinkedHashSet
4 3 Exemples d'utilisation
https://blog.paumard.org/cours/java-api/chap05-concurrent-queues.html 7/8
12/12/2023, 11:18 7. Files d'attente
4.3. Exemples d utilisation
// temporisation pour stopper observer le système 20 secondes
try { 5. Interface SortedSet
Thread.sleep(20000) ; 5.1. Notion de SortedSet
} catch (InterruptedException e) { 5 2 Dét il d éth d
}

// arrêt de notre système


for (int i = 0 ; i < boulangers.length ; i++) {
// interruption des producteurs
boulangers[i].interrupt() ;
} Java A P I avancées

// dépôt des pilules empoisonnées Cour s & T utoriaux


for (int i = 0 ; i < mangeurs.length ; i++) {
Reto u r a u blog Java le soir
boulangerie.deposePainEmpoisonne() ;
} Somm a i r e
P. p r é c é d ente P. s u i v a n te
On ajoute la méthode deposePainEmpoisonne() suivante à notre classe Boulangerie.
Table des matières
Exemple 81. Pilule empoisonnée : ajout de la méthode d'arrêt à la file d'attente
API Collection
1. Introduction
// on ajoute simplement le pain empoisonné à la file d'attente
2. Interface Collection
public void deposePainEmpoisonne() {
2.1. Notion de Collection
queue.add(Pain.PAIN_EMPOISONNE) ;
2.2. Détail des méthodes disponibles
}
2.3. Interface Iterator
2.4. Implémentation, exemples
d'utilisation
Si l'on fait fonctionner ce nouveau système, on constate bien que nos clients mourront, une fois que la file
d'attente a été épuisée. Ce système d'arrêt ressemble un peu à un roman d'Agatha Christie, où l'on 3. Interface List
empoisonne sans coup férir d'honnêtes consommateurs. Il est efficace, mais nécessite tout de même d'avoir
3.1. Notion de List
en permanence la connaissance du nombre de consommateurs actifs.
3.2. Détail des méthodes disponibles
3.3. Interface ListIterator
José Paumard © 2013, tous droits réservés
3.4. Implémentations, exemples
d'utilisation

4. Interface Set
4.1. Notion de Set
4.2. Implémentations HashSet et
LinkedHashSet
4 3 Exemples d'utilisation
https://blog.paumard.org/cours/java-api/chap05-concurrent-queues.html 8/8

Vous aimerez peut-être aussi