La file d'attente est un tableau linéaire spécial qui suit le principe du "premier entré, premier sorti". Dans notre utilisation quotidienne, nous l'utilisons souvent pour manipuler des données simultanément. En programmation concurrente, il est parfois nécessaire d'utiliser des files d'attente thread-safe. Si vous souhaitez implémenter une file d'attente thread-safe, il existe généralement deux manières : l'une consiste à utiliser une file d'attente de blocage et l'autre consiste à utiliser un verrou de synchronisation de thread.
Qu'est-ce qu'une file d'attente bloquante ?
Supposons qu'il y ait une boulangerie avec un client qui mange du pain et un chef qui prépare du pain. Un maximum de 2 miches de pain peuvent être placées dans le panier. Une fois le test terminé, le chef met le pain dans le panier et lorsque les invités mangent le pain, ils le sortent du panier afin de s'en assurer. il y a du pain dans le panier lorsque les invités mangent le pain ou le panier ne déborde pas lorsque le chef fait cuire le pain. À ce stade, nous devons introduire le concept de file d'attente bloquante, ce que nous appelons souvent le modèle producteur-consommateur. .
Une file d'attente bloquante est une file d'attente qui prend en charge deux opérations supplémentaires. Ces deux opérations supplémentaires prennent en charge les méthodes de blocage d’insertion et de suppression.
(1) Prise en charge de la méthode d'insertion bloquante : ce qui signifie que lorsque la file d'attente est pleine, la file d'attente bloquera le thread insérant des éléments jusqu'à ce que la file d'attente ne soit pas pleine.
(2) Prend en charge la méthode de suppression de blocage : cela signifie que lorsque la file d'attente est vide, le thread qui obtient l'élément attendra que la file d'attente ne devienne pas vide. Les files d'attente de blocage sont souvent utilisées dans les scénarios de producteur et de consommateur. Le producteur est le thread qui ajoute des éléments à la file d'attente et le consommateur est le thread qui extrait les éléments de la file d'attente. Une file d'attente de blocage est un conteneur utilisé par les producteurs pour stocker des éléments et par les consommateurs pour obtenir des éléments.
Aucune file d'attente bloquante dans le système : PriorityQueue et ConcurrentLinkedQueue
Jetons un coup d'œil à la relation entre les files d'attente non bloquantes (en prenant PriorityQueue comme exemple) :
La classe PriorityQueue hérite de AbstractQueue et implémente l'interface Serialisable. Maintenant essentiellement une liste ordonnée, PriorityQueue se trouve dans le package utilitaire Java. Le mot Priority dans la première moitié de son nom signifie priorité. En fait, cette file d'attente a "priorité". Les éléments ajoutés à la file d'attente sont positionnés selon leur ordre naturel (via son implémentation java.util.Comparable) ou selon l'implémentation java.util.Comparator transmise au constructeur.
ConcurrentLinkedQueue est une file d'attente thread-safe basée sur des nœuds liés. L'accès simultané ne nécessite pas de synchronisation. Parce qu'il ajoute des éléments à la queue de la file d'attente et les supprime de la tête, l'accès partagé de ConcurrentLinkedQueue à une collection commune fonctionne très bien sans connaître la taille de la file d'attente. La collecte d'informations sur la taille de la file d'attente sera lente et nécessite de parcourir la file d'attente ; ConcurrentLinkedQueue est une file d'attente thread-safe illimitée basée sur des nœuds liés. Elle utilise une règle du premier entré, premier sorti pour trier les nœuds. il sera ajouté à la queue de la file d'attente ; lorsque nous obtenons un élément, il renvoie l'élément en tête de la file d'attente.
File d'attente qui implémente l'interface de blocage :
L'interface BlockingQueue et cinq classes de file d'attente de blocage sont ajoutées à java.util.concurrent. Il s'agit essentiellement d'une structure de données FIFO avec une particularité. Plutôt que d'ajouter ou de supprimer immédiatement des éléments de la file d'attente, le thread exécutant l'opération se bloque jusqu'à ce qu'un espace ou un élément devienne disponible.
Les cinq files d'attente en proposent différentes :
·ArrayBlockingQueue : une file d'attente limitée soutenue par un tableau.
·LinkedBlockingQueue : une file d'attente délimitée facultative soutenue par des nœuds liés.
·PriorityBlockingQueue : une file d'attente prioritaire illimitée soutenue par un tas prioritaire.
·DelayQueue : une file d'attente de planification basée sur le temps soutenue par un tas prioritaire.
·SynchronousQueue : Un mécanisme de rendez-vous simple utilisant l'interface BlockingQueue.
Jetons un coup d'œil à la relation d'héritage entre ArrayBlockingQueue et LinkedBlockingQueue :
En examinant la relation d'héritage entre les deux classes, nous pouvons savoir qu'elles héritent également de AbstractQueue et implémentent l'interface Serialisable ; la différence est qu'elles implémentent également l'interface BlockingQueue.
Une brève introduction à certains d'entre eux :
LinkedBlockingQueueLa taille par défaut de LinkedBlockingQueue est Integer.MAX_VALUE, qui peut être comprise comme une file d'attente délimitée en cache. Vous pouvez choisir de spécifier sa capacité maximale. Il s'agit d'une file d'attente basée sur une liste chaînée. (premier entré, premier sorti). Lorsque le producteur place une donnée dans la file d'attente, elle est mise en cache dans la file d'attente. Lorsque le tampon de la file d'attente atteint la capacité maximale du cache (LinkedBlockingQueue peut spécifier cette valeur via le constructeur), la file d'attente du producteur est bloquée jusqu'à ce que le consommateur consomme du. file d'attente Lorsqu'une donnée est supprimée, le thread producteur sera réveillé, et vice versa pour les consommateurs.
ArrayBlockingQueue doit spécifier la capacité lors de la construction, et vous pouvez choisir si l'équité est requise. Si le paramètre d'équité est défini sur true, le thread avec le temps d'attente le plus long sera traité en premier (en fait, cela est obtenu en définissant ReentrantLock sur). vrai Ce genre d'équité : c'est-à-dire que le thread avec le temps d'attente le plus long fonctionnera en premier). Généralement, l’équité vous coûtera en performance, alors utilisez-la uniquement lorsque vous en avez vraiment besoin. Il s'agit d'une file d'attente circulaire de blocage basée sur un tableau qui trie les éléments selon le principe FIFO (premier entré, premier sorti).
PriorityBlockingQueue est une file d'attente prioritaire, pas une file d'attente premier entré, premier sorti. Les éléments sont supprimés par ordre de priorité et la file d'attente n'a pas de limite supérieure (après avoir examiné le code source, PriorityBlockingQueue est un repackage de PriorityQueue, basé sur la structure de données du tas, et PriorityQueue n'a pas de limite de capacité, comme ArrayList, donc en priorité Elle ne sera pas bloquée lors de la mise en file d'attente de blocage. Bien que cette file d'attente soit logiquement illimitée, tenter d'effectuer une opération d'ajout peut provoquer une OutOfMemoryError car les ressources sont épuisées), mais si la file d'attente est vide, l'opération de prise du L'élément est bloqué, donc son opération de récupération est bloquée. De plus, les éléments entrant dans la file d'attente doivent avoir des capacités de comparaison.
À propos de ConcurrentLinkedQueue et LinkedBlockingQueue :
peut également être compris comme la différence entre la file d'attente bloquante et la file d'attente non bloquante :
1. mécanisme de verrouillage. ConcurrentLinkedQueue utilise l'algorithme CAS, bien que l'acquisition du verrou sous-jacent de LinkedBlockingQueue utilise également l'algorithme CAS.
2. Concernant la récupération d'éléments, ConcurrentLinkedQueue ne prend pas en charge le blocage pour récupérer des éléments, et LinkedBlockingQueue prend en charge la méthode de blocage take().
3. Concernant les performances d'insertion d'éléments, en utilisation réelle, notamment sur les serveurs multi-CPU, la différence entre verrouillage et sans verrouillage se reflètera beaucoup plus rapidement que LinkedBlockingQueue.
Code producteur-consommateur :
J'ai vu un petit exemple de producteur-consommateur sur Internet, ce qui est très utile pour comprendre les files d'attente de blocage. Le code est le suivant. suit :
import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.BlockingQueue; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class BlockingQueueTest { public static class Basket { BlockingQueue<String> basket = new ArrayBlockingQueue<>(3); private void produce() throws InterruptedException { basket.put("苹果"); } private void consume() throws InterruptedException { basket.take(); } private int getAppleNumber() { return basket.size(); } } private static void testBasket() { final Basket basket = new Basket(); class Producer implements Runnable { public void run() { try { while (true) { System.out.println("生产者开始生产苹果###"); basket.produce(); System.out.println("生产者生产苹果完毕###"); System.out.println("篮子中的苹果数量:" + basket.getAppleNumber() + "个"); Thread.sleep(300); } } catch (InterruptedException e) {} } } class Consumer implements Runnable { public void run() { try { while (true) { System.out.println("消费者开始消费苹果***"); basket.consume(); System.out.println("消费者消费苹果完毕***"); System.out.println("篮子中的苹果数量:" + basket.getAppleNumber() + "个"); Thread.sleep(1000); } } catch (InterruptedException e) {} } } ExecutorService service = Executors.newCachedThreadPool(); Producer producer = new Producer(); Consumer consumer = new Consumer(); service.submit(producer); service.submit(consumer); try { Thread.sleep(10000); } catch (InterruptedException e) {} service.shutdownNow(); } public static void main(String[] args) { BlockingQueueTest.testBasket(); } }
De nombreusesvidéos de formation Java, toutes sur le site PHP chinois, bienvenue pour apprendre en ligne !
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!