Guide de PriorityBlockingQueue en Java

1. Introduction

Dans cet article, nous allons nous concentrer sur la classe PriorityBlockingQueue et passer en revue quelques exemples pratiques.

En partant de l'hypothèse que nous savons déjà ce qu'est une file d'attente , nous allons d'abord montrer comment les éléments de PriorityBlockingQueue sont classés par priorité .

Ensuite, nous montrerons comment ce type de file d'attente peut être utilisé pour bloquer un thread.

Enfin, nous montrerons comment l'utilisation conjointe de ces deux fonctionnalités peut être utile lors du traitement de données sur plusieurs threads.

2. Priorité des éléments

Contrairement à une file d'attente standard, vous ne pouvez pas simplement ajouter n'importe quel type d'élément à une PriorityBlockingQueue. Il existe deux options:

  1. Ajout d'éléments qui implémentent Comparable
  2. Ajout d' éléments qui ne mettent pas en œuvre Comparable , à condition que vous fournissez un Comparator aussi bien

En utilisant les implémentations Comparator ou Comparable pour comparer des éléments, PriorityBlockingQueue sera toujours trié.

Le but est de mettre en œuvre une logique de comparaison de manière à ce que l'élément de priorité la plus élevée soit toujours ordonné en premier . Ensuite, lorsque nous supprimons un élément de notre file d'attente, ce sera toujours celui avec la priorité la plus élevée.

Pour commencer, utilisons notre file d'attente dans un seul thread, au lieu de l'utiliser sur plusieurs. En faisant cela, il est facile de prouver comment les éléments sont ordonnés dans un test unitaire:

PriorityBlockingQueue queue = new PriorityBlockingQueue(); ArrayList polledElements = new ArrayList(); queue.add(1); queue.add(5); queue.add(2); queue.add(3); queue.add(4); queue.drainTo(polledElements); assertThat(polledElements).containsExactly(1, 2, 3, 4, 5);

Comme nous pouvons le voir, malgré l'ajout des éléments à la file d'attente dans un ordre aléatoire, ils seront classés lorsque nous commencerons à les interroger. En effet, la classe Integer implémente Comparable, qui sera à son tour utilisée pour s'assurer que nous les retirons de la file d'attente dans l'ordre croissant.

Il convient également de noter que lorsque deux éléments sont comparés et sont identiques, il n'y a aucune garantie de la façon dont ils seront commandés.

3. Utilisation de la file d' attente pour bloquer

Si nous avions affaire à une file d'attente standard, nous appellerions poll () pour récupérer des éléments. Cependant, si la file d'attente était vide, un appel à poll () renverrait null.

Le PriorityBlockingQueue met en œuvre la BlockingQueue interface, qui nous donne quelques méthodes supplémentaires qui nous permettent de bloquer lors de la suppression d'une file d' attente vide . Essayons d'utiliser la méthode take () , qui devrait faire exactement cela:

PriorityBlockingQueue queue = new PriorityBlockingQueue(); new Thread(() -> { System.out.println("Polling..."); try { Integer poll = queue.take(); System.out.println("Polled: " + poll); } catch (InterruptedException e) { e.printStackTrace(); } }).start(); Thread.sleep(TimeUnit.SECONDS.toMillis(5)); System.out.println("Adding to queue"); queue.add(1);

Bien que l'utilisation de sleep () soit une manière légèrement fragile de démontrer les choses, lorsque nous exécuterons ce code, nous verrons:

Polling... Adding to queue Polled: 1 

Cela prouve que take () a bloqué jusqu'à ce qu'un élément soit ajouté:

  1. Le fil imprimera «Polling» pour prouver qu'il a commencé
  2. Le test se mettra ensuite en pause pendant environ cinq secondes, pour prouver que le thread doit avoir appelé take () à ce stade
  3. Nous ajoutons à la file d'attente, et devrions voir plus ou moins instantanément «Polled: 1» pour prouver que take () a renvoyé un élément dès qu'il est disponible

Il convient également de mentionner que l' interface BlockingQueue nous fournit également des moyens de bloquer lors de l'ajout à des files d'attente pleines.

Cependant, un PriorityBlockingQueue est illimité. Cela signifie qu'il ne sera jamais plein, il sera donc toujours possible d'ajouter de nouveaux éléments.

4. Utilisation conjointe du blocage et de la priorisation

Maintenant que nous avons expliqué les deux concepts clés d'un PriorityBlockingQueue, utilisons-les tous les deux ensemble. Nous pouvons simplement développer notre exemple précédent, mais cette fois, ajoutez plus d'éléments à la file d'attente:

Thread thread = new Thread(() -> { System.out.println("Polling..."); while (true) { try { Integer poll = queue.take(); System.out.println("Polled: " + poll); } catch (InterruptedException e) { e.printStackTrace(); } } }); thread.start(); Thread.sleep(TimeUnit.SECONDS.toMillis(5)); System.out.println("Adding to queue"); queue.addAll(newArrayList(1, 5, 6, 1, 2, 6, 7)); Thread.sleep(TimeUnit.SECONDS.toMillis(1));

Encore une fois, bien que ce soit un peu fragile à cause de l'utilisation de sleep (), cela nous montre toujours un cas d'utilisation valide. Nous avons maintenant une file d'attente qui se bloque en attendant l'ajout d'éléments. Nous ajoutons ensuite de nombreux éléments à la fois, puis nous montrons qu'ils seront traités par ordre de priorité. La sortie ressemblera à ceci:

Polling... Adding to queue Polled: 1 Polled: 1 Polled: 2 Polled: 5 Polled: 6 Polled: 6 Polled: 7

5. Conclusion

Dans ce guide, nous avons montré comment nous pouvons utiliser une PriorityBlockingQueue afin de bloquer un thread jusqu'à ce que certains éléments y soient ajoutés, et également que nous sommes en mesure de traiter ces éléments en fonction de leur priorité.

L'implémentation de ces exemples est disponible à l'adresse over sur GitHub. Il s'agit d'un projet basé sur Maven, il devrait donc être facile à exécuter tel quel.