Quelle implémentation de file d'attente simultanée dois-je utiliser en Java?

132

À partir des JavaDocs:

  • Un ConcurrentLinkedQueue est un choix approprié lorsque de nombreux threads partageront l'accès à une collection commune. Cette file d'attente n'autorise pas les éléments nuls.
  • ArrayBlockingQueue est un "tampon borné" classique, dans lequel un tableau de taille fixe contient des éléments insérés par les producteurs et extraits par les consommateurs. Cette classe prend en charge une stratégie d'équité facultative pour la commande des threads producteurs et consommateurs en attente
  • LinkedBlockingQueue a généralement un débit plus élevé que les files d'attente basées sur un tableau, mais des performances moins prévisibles dans la plupart des applications simultanées.

J'ai 2 scénarios, l'un nécessite que la file d'attente prenne en charge de nombreux producteurs (threads l'utilisant) avec un consommateur et l'autre est l'inverse.

Je ne comprends pas quelle implémentation utiliser. Quelqu'un peut-il expliquer quelles sont les différences?

Aussi, quelle est la «politique d'équité facultative» dans le ArrayBlockingQueue?

David Hofmann
la source
1
Vous avez également oublié de poser des questions sur PriorityBlockingQueue, ce qui est utile pour spécifier un ordre dans lequel les threads sont traités.
IgorGanapolsky

Réponses:

53

Fondamentalement, la différence entre eux réside dans les caractéristiques de performance et le comportement de blocage.

Le plus simple est d'abord ArrayBlockingQueueune file d'attente de taille fixe. Donc, si vous définissez la taille sur 10 et essayez d'insérer un 11e élément, l'instruction d'insertion se bloquera jusqu'à ce qu'un autre thread supprime un élément. Le problème d'équité est ce qui se passe si plusieurs threads tentent d'insérer et de supprimer en même temps (en d'autres termes pendant la période où la file d'attente a été bloquée). Un algorithme d'équité garantit que le premier thread qui demande est le premier thread qui obtient. Sinon, un thread donné peut attendre plus longtemps que les autres threads, provoquant un comportement imprévisible (parfois un thread ne prendra que quelques secondes car les autres threads qui ont démarré plus tard ont été traités en premier). Le compromis est qu'il faut des frais généraux pour gérer l'équité, ce qui ralentit le débit.

La différence la plus importante entre LinkedBlockingQueueet ConcurrentLinkedQueueest que si vous demandez un élément à a LinkedBlockingQueueet que la file d'attente est vide, votre thread attendra jusqu'à ce qu'il y ait quelque chose. A ConcurrentLinkedQueuereviendra tout de suite avec le comportement d'une file d'attente vide.

Lequel dépend si vous avez besoin du blocage. Là où vous avez de nombreux producteurs et un seul consommateur, cela ressemble à ça. D'un autre côté, lorsque vous avez de nombreux consommateurs et un seul producteur, vous n'aurez peut-être pas besoin du comportement de blocage, et serez peut-être heureux de simplement demander aux consommateurs de vérifier si la file d'attente est vide et de continuer si c'est le cas.

Yishai
la source
67
La réponse est trompeuse. LinkedBlockingQueue et ConcurrentLinkedQueue ont la méthode "poll ()" qui supprime la tête de la file d'attente ou retourne null (ne bloque pas) et la méthode "offer (E e)" qui s'insère dans la queue de la file et ne bloque pas. La différence est que seul LinkedBlockingQueue a des opérations de blocage en plus des opérations non bloquantes - et pour ce privilège, vous payez le prix que LinkedBlockingQueue a en fait un verrouillage. L'autre réponse explique cela.
Nakedible
123

ConcurrentLinkedQueue signifie qu'aucun verrou n'est pris (c'est-à-dire qu'aucun appel synchronisé (this) ou Lock.lock ). Il utilisera une opération CAS - Compare and Swap pendant les modifications pour voir si le nœud tête / queue est toujours le même qu'au démarrage. Si tel est le cas, l'opération réussit. Si le nœud tête / queue est différent, il tournera et réessayera.

LinkedBlockingQueue prendra un verrou avant toute modification. Ainsi, vos appels d'offres seraient bloqués jusqu'à ce qu'ils obtiennent le verrou. Vous pouvez utiliser la surcharge d'offre qui prend un TimeUnit pour dire que vous êtes seulement prêt à attendre X temps avant d'abandonner l'ajout (généralement bon pour les files d'attente de type de message où le message est périmé après X nombre de millisecondes).

L'équité signifie que l'implémentation Lock gardera les threads ordonnés. Cela signifie que si le fil A entre puis que le fil B entre, le fil A obtiendra le verrou en premier. Sans équité, ce qui se passe n'est pas vraiment défini. Ce sera probablement le prochain thread qui sera programmé.

Quant à savoir lequel utiliser, cela dépend. J'ai tendance à utiliser ConcurrentLinkedQueue car le temps qu'il faut à mes producteurs pour mettre du travail dans la file d'attente est varié. Je n'ai pas beaucoup de producteurs qui produisent exactement au même moment. Mais le côté consommateur est plus compliqué car le sondage ne passera pas dans un bon état de sommeil. Vous devez gérer cela vous-même.

Justin Rudd
la source
1
Et dans quelles conditions ArrayBlockingQueue est meilleur que LinkedBlockingQueue?
kolobok le
@akapelko ArrayBlockingQueue permet un ordre plus fin.
IgorGanapolsky
2
Qu'est-ce que cela signifie - "il va tourner et essayer à nouveau." ?
Lester
9

Le titre de votre question mentionne le blocage des files d'attente. Cependant, ce ConcurrentLinkedQueuen'est pas une file d'attente bloquante.

Les BlockingQueues sont ArrayBlockingQueue, DelayQueue, LinkedBlockingDeque, LinkedBlockingQueue, PriorityBlockingQueueet SynchronousQueue.

Certains d' entre eux ne sont manifestement pas en forme à vos besoins ( DelayQueue, PriorityBlockingQueueet SynchronousQueue). LinkedBlockingQueueet LinkedBlockingDequesont identiques, sauf que cette dernière est une file d'attente à deux extrémités (elle implémente l'interface Deque).

Puisque ce ArrayBlockingQueuen'est utile que si vous souhaitez limiter le nombre d'éléments, je m'en tiens à LinkedBlockingQueue.

Powerlord
la source
J'ai supprimé le mot de blocage du titre, merci. Permettez-moi de voir si je l'ai compris, ce que vous avez dit signifie que LinkedBlockingQueue peut être utilisé dans des consommateurs multiples / produit des scénarios sur le même objet?
David Hofmann
1
Je pensais que ArrayBlockingQueue permettait un ordre plus fin des threads? D'où son avantage.
IgorGanapolsky
4

ArrayBlockingQueue a une empreinte mémoire inférieure, il peut réutiliser le nœud d'élément, contrairement à LinkedBlockingQueue qui doit créer un objet LinkedBlockingQueue $ Node pour chaque nouvelle insertion.

Deng
la source
1
bon point! Je préfère ArrayBlockingQueue plus que LinkedBlockingQueue
trillions
2
Ce n'est pas nécessairement vrai - si votre file d'attente est sur le point de se vider la plupart du temps mais doit pouvoir grossir, ArrayBlockingQueueelle aura une empreinte mémoire bien pire - elle a toujours un grand tableau alloué en mémoire tout le temps, tandis que le LinkedBlockingQueueaura une empreinte mémoire négligeable lorsqu'il est presque vide.
Krease
1
  1. SynchronousQueue(Tiré d'une autre question )

SynchronousQueueest plus un transfert, alors que le LinkedBlockingQueueseul permet un seul élément. La différence étant que l' put()appel à a SynchronousQueuene reviendra pas tant qu'il n'y aura pas d' take()appel correspondant , mais avec un LinkedBlockingQueuede taille 1, l' put()appel (vers une file d'attente vide) reviendra immédiatement. C'est essentiellement l' BlockingQueueimplémentation lorsque vous ne voulez pas vraiment de file d'attente (vous ne voulez pas conserver de données en attente).

  1. LinkedBlockingQueue( LinkedListMise en œuvre mais pas exactement L'implémentation JDK de LinkedListIt utilise la classe interne statique Node pour maintenir les liens entre les éléments)

Constructeur pour LinkedBlockingQueue

public LinkedBlockingQueue(int capacity) 
{
        if (capacity < = 0) throw new IllegalArgumentException();
        this.capacity = capacity;
        last = head = new Node< E >(null);   // Maintains a underlying linkedlist. ( Use when size is not known )
}

Classe de nœud utilisée pour gérer les liens

static class Node<E> {
    E item;
    Node<E> next;
    Node(E x) { item = x; }
}

3. ArrayBlockingQueue (implémentation de tableau)

Constructeur pour ArrayBlockingQueue

public ArrayBlockingQueue(int capacity, boolean fair) 
{
            if (capacity < = 0)
                throw new IllegalArgumentException();
            this.items = new Object[capacity]; // Maintains a underlying array
            lock = new ReentrantLock(fair);
            notEmpty = lock.newCondition();
            notFull =  lock.newCondition();
}

IMHO La plus grande différence entre ArrayBlockingQueueet LinkedBlockingQueueest claire du constructeur on a la structure de données sous-jacente Array et autre linkedList .

ArrayBlockingQueueutilise l' algorithme de double condition à un seul verrou et LinkedBlockingQueueest une variante de l' algorithme "deux verrous file d'attente" et il a 2 verrous 2 conditions (takeLock, putLock)

Vipin
la source
0

ConcurrentLinkedQueue est sans verrouillage, LinkedBlockingQueue ne l'est pas. Chaque fois que vous appelez LinkedBlockingQueue.put () ou LinkedBlockingQueue.take (), vous devez d'abord acquérir le verrou. En d'autres termes, LinkedBlockingQueue a une mauvaise concurrence. Si vous vous souciez des performances, essayez ConcurrentLinkedQueue + LockSupport.

user319609
la source