web-dev-qa-db-fra.com

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

Depuis les JavaDocs:

  • Un ConcurrentLinkedQueue est un choix approprié lorsque de nombreux threads partagent l'accès à une collection commune. Cette file d'attente n'autorise pas les éléments nuls.
  • ArrayBlockingQueue est un "tampon limité" 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 politique d’équité facultative pour la commande de fils de producteur et de consommateur en attente.
  • LinkedBlockingQueue ont 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 requiert la file d'attente pour supporter de nombreux producteurs (les 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?

En outre, quelle est la "politique d'équité optionnelle" dans le ArrayBlockingQueue?

122
David Hofmann

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

Prenant le plus simple en premier, ArrayBlockingQueue est une file d’attente de taille fixe. Ainsi, si vous définissez la taille sur 10 et tentez d'insérer un onzième élément, l'instruction d'insertion sera bloquée 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 était bloquée). Un algorithme d'équité garantit que le premier thread demandé est le premier thread obtenu. Sinon, un thread donné peut attendre plus longtemps que les autres threads, ce qui entraîne un comportement imprévisible (un thread prend parfois plusieurs secondes, car les autres threads démarrés 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 LinkedBlockingQueue et ConcurrentLinkedQueue est que si vous demandez un élément à un LinkedBlockingQueue et que la file d'attente est vide, votre thread attendra qu'il y ait quelque chose. Un ConcurrentLinkedQueue retournera immédiatement avec le comportement d'une file vide.

Lequel dépend si vous avez besoin du blocage. Là où il y a plusieurs producteurs et un seul consommateur, cela semble être le cas. D'autre part, lorsque vous avez plusieurs consommateurs et un seul producteur, vous n'avez peut-être pas besoin du comportement de blocage et vous pouvez simplement demander aux consommateurs de vérifier si la file d'attente est vide et de passer à autre chose si c'est le cas.

47
Yishai

ConcurrentLinkedQueue signifie qu'aucun verrou n'est utilisé (c'est-à-dire aucun appel synchronisé (this) ou Lock.lock ). Il utilisera une opération CAS - Compare and Swap lors des modifications pour voir si le nœud head/tail est toujours le même qu’il a commencé. Si c'est le cas, l'opération réussit. Si le nœud head/tail est différent, il tournera et essaiera à nouveau.

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

L'équité signifie que l'implémentation de verrouillage maintiendra les threads ordonnés. Cela signifie que si le fil A entre et que le fil B entre, le fil A obtiendra le verrou en premier. Sans équité, ce qui se passe n’est vraiment pas défini. Ce sera probablement le prochain fil qui sera planifié.

Quant à celui à utiliser, cela dépend. J'ai tendance à utiliser ConcurrentLinkedQueue parce que le temps qu'il faut à mes producteurs pour trouver du travail à mettre en 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 n’ira pas dans un état de sommeil agréable. Vous devez gérer cela vous-même.

109
Justin Rudd

Le titre de votre question mentionne les files d'attente bloquantes. Cependant, ConcurrentLinkedQueue n'est pas une file d'attente bloquante.

Les BlockingQueue sont ArrayBlockingQueue, DelayQueue, LinkedBlockingDeque, LinkedBlockingQueue, PriorityBlockingQueue et SynchronousQueue.

Certains d'entre eux ne sont clairement pas adaptés à vos besoins (DelayQueue, PriorityBlockingQueue et SynchronousQueue.). LinkedBlockingQueue et LinkedBlockingDeque sont identiques, sauf que cette dernière est une file d'attente à deux extrémités (elle implémente l'interface Deque).

Puisque ArrayBlockingQueue n’est utile que si vous voulez limiter le nombre d’éléments, je me contenterais de LinkedBlockingQueue.

8
Powerlord

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

4
Deng
  1. SynchronousQueue (extrait d'un autre question )

SynchronousQueue est plus un transfert, alors que LinkedBlockingQueue n'autorise qu'un seul élément. La différence étant que l'appel put() à un SynchronousQueue ne sera pas renvoyé tant qu'il n'y aura pas d'appel correspondant take(), mais avec un LinkedBlockingQueue de taille 1, Un appel put() (vers une file vide) reviendra immédiatement. C'est essentiellement l'implémentation BlockingQueue lorsque vous ne voulez pas vraiment de file d'attente (vous ne voulez pas conserver les données en attente).

  1. LinkedBlockingQueue (LinkedList implémentation mais pas exactement JDK Implémentation de LinkedList utilise une classe interne statique Node pour gérer 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 ArrayBlockingQueue et LinkedBlockingQueue est claire à partir du constructeur on a la structure de données sous-jacente Array et une autre linkedList .

ArrayBlockingQueue utilise algorithme à simple verrouillage à deux conditions et LinkedBlockingQueue est une variante de l'algorithme "deux files d'attente" et comporte 2 conditions de verrouillage (2) (takeLock, putLock)

1
Vipin

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

0
user319609