web-dev-qa-db-fra.com

LinkedBlockingQueue vs ConcurrentLinkedQueue

Ma question concerne cette question posée plus tôt. Dans les situations où j'utilise une file d'attente pour la communication entre les threads producteur et consommateur, les gens recommandent-ils généralement d'utiliser LinkedBlockingQueue ou ConcurrentLinkedQueue?

Quels sont les avantages/inconvénients d'utiliser l'un par rapport à l'autre?

La principale différence que je peux voir du point de vue de l'API est qu'un LinkedBlockingQueue peut éventuellement être délimité.

101
Adamski

Pour un thread producteur/consommateur, je ne suis pas sûr que ConcurrentLinkedQueue soit même une option raisonnable - il n'implémente pas BlockingQueue, qui est l'interface fondamentale pour les files d'attente producteur/consommateur IMO. Vous devez appeler poll(), attendre un peu si vous n'avez rien trouvé, puis interroger à nouveau, etc. entraînant des retards lorsqu'un nouvel élément arrive et des inefficacités lorsqu'il est vide (en raison se réveiller inutilement du sommeil).

Dans les documents de BlockingQueue:

BlockingQueue les implémentations sont conçues pour être utilisées principalement pour les files d'attente producteur-consommateur

Je sais que cela ne strictement dit que seules les files d'attente de blocage doivent être utilisées pour les files d'attente producteur-consommateur, mais même ainsi ...

99
Jon Skeet

Cette question mérite une meilleure réponse.

ConcurrentLinkedQueue de Java est basé sur les fameuses files d'attente algorithme de Maged M. Michael et Michael L. Scott pour sans verrouillage sans blocage .

"Non-bloquant" comme terme ici pour une ressource contenue (notre file d'attente) signifie que, indépendamment de ce que fait le planificateur de la plateforme, comme interrompre un thread, ou si le thread en question est tout simplement trop lent, d'autres threads se disputent la même ressource sera toujours en mesure de progresser. Si un verrou est impliqué par exemple, le thread tenant le verrou pourrait être interrompu et tous les threads en attente de ce verrouillage seraient bloqués. Les verrous intrinsèques (le mot clé synchronized) dans Java peuvent également entraîner une grave pénalité pour les performances - comme lorsque verrouillage biaisé est impliqué et que vous avez contention, ou après la VM décide de "gonfler" le verrou après une période de grâce de rotation et de bloquer les threads rivaux ... c'est pourquoi dans de nombreux contextes (scénarios de contention faible/moyenne), faire comparer et définir des références atomiques peut être beaucoup plus efficace et c'est exactement ce que font de nombreuses structures de données non bloquantes.

ConcurrentLinkedQueue de Java est non seulement non bloquant, mais il a la propriété impressionnante que le producteur ne conteste pas avec le consommateur. Dans un scénario de producteur/consommateur unique (SPSC), cela signifie vraiment qu'il n'y aura pas de conflit à proprement parler. Dans un scénario à plusieurs producteurs/un seul consommateur, le consommateur ne fera pas face aux producteurs. Cette file d'attente est conflictuelle lorsque plusieurs producteurs tentent de offer(), mais c'est une concurrence par définition. Il s'agit essentiellement d'une file d'attente non bloquante à usage général et efficace.

Quant à ce qu'il ne s'agit pas d'un BlockingQueue, eh bien, bloquer un thread pour attendre dans une file d'attente est une façon terriblement terrible de concevoir des systèmes concurrents. Non. Si vous ne savez pas comment utiliser un ConcurrentLinkedQueue dans un scénario consommateur/producteur, passez simplement à des abstractions de niveau supérieur, comme un bon framework d'acteur.

62
Alexandru Nedelcu

LinkedBlockingQueue bloque le consommateur ou le producteur lorsque la file d'attente est vide ou pleine et que le thread consommateur/producteur respectif est mis en veille. Mais cette fonction de blocage a un coût: chaque opération de vente ou d'achat est un verrou opposé entre les producteurs ou les consommateurs (si nombreux), donc dans les scénarios avec de nombreux producteurs/consommateurs, l'opération peut être plus lente.

ConcurrentLinkedQueue n'utilise pas de verrous, mais CAS , sur ses opérations put/take, réduisant potentiellement les conflits avec de nombreux threads producteurs et consommateurs. Mais étant une structure de données "sans attente", ConcurrentLinkedQueue ne se bloquera pas lorsqu'elle est vide, ce qui signifie que le consommateur devra traiter les valeurs de take() renvoyant null par "occupé" en attente ", par exemple, avec le thread consommateur consommant du CPU.

Ainsi, lequel est "meilleur" dépend du nombre de fils de consommation, du taux qu'ils consomment/produisent, etc. Une référence est nécessaire pour chaque scénario.

Un cas d'utilisation particulier où le ConcurrentLinkedQueue est clairement meilleur est lorsque les producteurs produisent d'abord quelque chose et terminent leur travail en plaçant le travail dans la file d'attente et seulement après le consommateur commence à consommer, sachant que ce sera fait lorsque la file d'attente sera vide. (ici il n'y a pas de concurrence entre producteur-consommateur mais seulement entre producteur-producteur et consommateur-consommateur)

25
dcernahoschi

Une autre solution (qui ne s'adapte pas bien) est les canaux de rendez-vous: Java.util.concurrent SynchronousQueue

1
Ovidiu Lupas

Si votre file d'attente n'est pas extensible et contient un seul thread producteur/consommateur. Vous pouvez utiliser une file d'attente sans verrou (vous n'avez pas besoin de verrouiller l'accès aux données).

0
Rahul