Comment utiliser ConcurrentLinkedQueue?

95

Comment utiliser un ConcurrentLinkedQueueen Java?
En utilisant cela LinkedQueue, dois-je m'inquiéter de la concurrence dans la file d'attente? Ou dois-je simplement définir deux méthodes (une pour récupérer des éléments de la liste et une autre pour ajouter des éléments à la liste)?
Remarque: ces deux méthodes doivent évidemment être synchronisées. Droite?


EDIT: Ce que j'essaie de faire est ceci: j'ai une classe (en Java) avec une méthode pour récupérer des éléments de la file d'attente et une autre classe avec une méthode pour ajouter des éléments à la file d'attente. Les éléments ajoutés et extraits de la liste sont des objets de ma propre classe.

Une autre question: dois-je faire cela dans la méthode remove:

while (queue.size() == 0){ 
  wait(); 
  queue.poll();
}

Je n'ai qu'un seul consommateur et un seul producteur.

Ricardo Felgueiras
la source
Merci pour la réponse à la question mt. Ce que j'essaye de faire est ceci: j'ai une classe (en Java) avec une méthode pour récupérer des éléments de la file d'attente et une autre classe avec une méthode pour ajouter des éléments à la file d'attente. Les éléments ajoutés et extraits de la liste sont des objets de ma propre classe.
Ricardo Felgueiras
2
Vous devriez modifier votre question et mettre cette clarification dans la question elle-même.
Adam Jaskiewicz

Réponses:

156

Non, les méthodes n'ont pas besoin d'être synchronisées et vous n'avez pas besoin de définir de méthodes; ils sont déjà dans ConcurrentLinkedQueue, utilisez-les simplement. ConcurrentLinkedQueue effectue toutes les opérations de verrouillage et autres dont vous avez besoin en interne; vos producteurs ajoutent des données dans la file d'attente et vos consommateurs les interrogent.

Tout d'abord, créez votre file d'attente:

Queue<YourObject> queue = new ConcurrentLinkedQueue<YourObject>();

Maintenant, où que vous créiez vos objets producteurs / consommateurs, passez dans la file d'attente pour qu'ils aient un endroit où placer leurs objets (vous pouvez utiliser un setter pour cela, à la place, mais je préfère faire ce genre de chose dans un constructeur):

YourProducer producer = new YourProducer(queue);

et:

YourConsumer consumer = new YourConsumer(queue);

et ajoutez-y des éléments dans votre producteur:

queue.offer(myObject);

et enlevez des choses dans votre consommateur (si la file d'attente est vide, poll () retournera null, alors vérifiez-le):

YourObject myObject = queue.poll();

Pour plus d'informations, consultez le Javadoc

ÉDITER:

Si vous devez bloquer l'attente pour que la file d'attente ne soit pas vide, vous souhaiterez probablement utiliser LinkedBlockingQueue et utiliser la méthode take (). Cependant, LinkedBlockingQueue a une capacité maximale (par défaut Integer.MAX_VALUE, qui est supérieur à deux milliards) et peut donc ou non être approprié en fonction de votre situation.

Si vous n'avez qu'un seul thread qui met des éléments dans la file d'attente et qu'un autre thread enlève des éléments de la file d'attente, ConcurrentLinkedQueue est probablement excessif. C'est plus lorsque vous pouvez avoir des centaines, voire des milliers de threads accédant à la file d'attente en même temps. Vos besoins seront probablement satisfaits en utilisant:

Queue<YourObject> queue = Collections.synchronizedList(new LinkedList<YourObject>());

Un avantage de ceci est qu'il se verrouille sur l'instance (file d'attente), vous pouvez donc synchroniser sur la file d'attente pour assurer l'atomicité des opérations composites (comme expliqué par Jared). Vous NE POUVEZ PAS faire cela avec un ConcurrentLinkedQueue, car toutes les opérations sont effectuées SANS verrouillage sur l'instance (à l'aide des variables java.util.concurrent.atomic). Vous n'aurez PAS besoin de faire cela si vous voulez bloquer pendant que la file d'attente est vide, car poll () renverra simplement null alors que la file d'attente est vide et poll () est atomique. Vérifiez si poll () renvoie null. Si c'est le cas, attendez (), puis réessayez. Pas besoin de verrouiller.

Finalement:

Honnêtement, j'utiliserais juste un LinkedBlockingQueue. C'est encore exagéré pour votre application, mais il y a de fortes chances que cela fonctionne correctement. Si ce n'est pas assez performant (PROFILE!), Vous pouvez toujours essayer autre chose, et cela signifie que vous n'avez pas à gérer AUCUN truc synchronisé:

BlockingQueue<YourObject> queue = new LinkedBlockingQueue<YourObject>();

queue.put(myObject); // Blocks until queue isn't full.

YourObject myObject = queue.take(); // Blocks until queue isn't empty.

Tout le reste est identique. Put ne bloquera probablement pas, car vous ne risquez pas de mettre deux milliards d'objets dans la file d'attente.

Adam Jaskiewicz
la source
Merci pour votre réponse. Une autre question: dois-je faire cela dans la méthode remove: while (queue.size () == 0) wait (); queue.poll ();
Ricardo Felgueiras
Je vais y répondre en tant que modification de ma réponse, car c'est assez important.
Adam Jaskiewicz
Comme cela a causé de la confusion dans une autre question, Collection.synchronizedListrenvoie un Listqui n'implémente pas Queue.
Tom Hawtin - tackline
@AdamJaskiewicz utilise ConcurrentLinkedQueuepour Producer Consumer une bonne idée, je fais référence à ce post stackoverflow.com/questions/1426754/…
rd22
37

C'est en grande partie le double d'une autre question .

Voici la section de cette réponse qui est pertinente à cette question:

Dois-je effectuer ma propre synchronisation si j'utilise java.util.ConcurrentLinkedQueue?

Les opérations atomiques sur les collections simultanées sont synchronisées pour vous. En d'autres termes, chaque appel individuel à la file d'attente est garanti thread-safe sans aucune action de votre part. Ce qui n'est pas garanti thread-safe, ce sont les opérations que vous effectuez sur la collection qui ne sont pas atomiques.

Par exemple, c'est threadsafe sans aucune action de votre part:

queue.add(obj);

ou

queue.poll(obj);

Toutefois; les appels non atomiques à la file d'attente ne sont pas automatiquement thread-safe. Par exemple, les opérations suivantes ne sont pas automatiquement threadsafe:

if(!queue.isEmpty()) {
   queue.poll(obj);
}

Ce dernier n'est pas threadsafe, car il est très possible qu'entre le moment où isEmpty est appelé et le moment où l'interrogation est appelée, d'autres threads aient ajouté ou supprimé des éléments de la file d'attente. La méthode threadsafe pour effectuer cela est la suivante:

synchronized(queue) {
    if(!queue.isEmpty()) {
       queue.poll(obj);
    }
}

Encore une fois ... les appels atomiques à la file d'attente sont automatiquement thread-safe. Les appels non atomiques ne le sont pas.

Jared
la source
1
La seule utilisation courante que je puisse faire est de bloquer jusqu'à ce que la file d'attente ne soit pas vide. Cela ne serait-il pas mieux servi en utilisant une implémentation de BlockingQueue (take () est atomique et bloque jusqu'à ce qu'il y ait quelque chose à consommer)?
Adam Jaskiewicz
6
Vous devez également faire attention à cela. Contrairement à synchronizedList, ConcurrentLinkedQueue ne se synchronise pas sur lui-même, donc dans votre code, il serait toujours possible pour un producteur de proposer à la file d'attente pendant que vous êtes dans votre bloc synchronisé.
Adam Jaskiewicz
Pourquoi combineriez-vous queue.isEmpty () et queue.poll ()? Ne pouvez-vous pas simplement interroger et vérifier si le résultat est nul? (je crois comprendre que si vous interrogez une file d'attente vide, il renvoie null)
AjahnCharles
Je suis d'accord avec tout dans votre code à l'exception du dernier bloc de code. La synchronisation sur ConcurrentLInkedQueue ne vous garantit rien car les autres appels ne sont pas synchronisés. Donc il pourrait y avoir un "add (..)" se produisant à partir d'un autre thread entre votre "isEmpty ()" et "poll (...)" même si vous avez synchronisé ce thread
Klitos G.
6

Utilisez poll pour obtenir le premier élément et add pour ajouter un nouveau dernier élément. Voilà, pas de synchronisation ou autre chose.

Hank Gay
la source
6

C'est probablement ce que vous recherchez en termes de sécurité des threads et de "jolie" lorsque vous essayez de tout consommer dans la file d'attente:

for (YourObject obj = queue.poll(); obj != null; obj = queue.poll()) {
}

Cela garantit que vous quittez lorsque la file d'attente est vide et que vous continuez à en retirer les objets tant qu'elle n'est pas vide.

marq
la source
Très utile pour vider une file d'attente. Merci.
DevilCode
2

Le ConcurentLinkedQueue est une implémentation sans attente / verrouillage très efficace (voir le javadoc pour référence), donc non seulement vous n'avez pas besoin de synchroniser, mais la file d'attente ne verrouillera rien, étant donc virtuellement aussi rapide qu'un non synchronisé (pas un thread safe) un.

siddhadev
la source
1

Utilisez-le simplement comme vous le feriez pour une collection non simultanée. Les classes Concurrent [Collection] encapsulent les collections régulières afin que vous n'ayez pas à penser à synchroniser l'accès.

Edit: ConcurrentLinkedList n'est pas en fait juste un wrapper, mais plutôt une meilleure implémentation simultanée. Quoi qu'il en soit, vous n'avez pas à vous soucier de la synchronisation.

Ben S
la source
ConcurrentLinkedQueue ne l'est pas. Il est spécialement conçu à partir de zéro pour un accès simultané par plusieurs producteurs et consommateurs. Un peu plus fantaisiste que les simples wrappers retournés par Collections.synchronized *
Adam Jaskiewicz
Oui, il y a eu beaucoup de choses
intéressantes en matière