Welche gleichzeitige Queue-Implementierung sollte ich in Java verwenden?

132

Aus den JavaDocs:

  • Eine ConcurrentLinkedQueue ist eine geeignete Wahl, wenn viele Threads gemeinsam auf eine gemeinsame Sammlung zugreifen. Diese Warteschlange erlaubt keine Nullelemente.
  • ArrayBlockingQueue ist ein klassischer "begrenzter Puffer", in dem ein Array mit fester Größe Elemente enthält, die von Produzenten eingefügt und von Verbrauchern extrahiert wurden. Diese Klasse unterstützt eine optionale Fairness-Richtlinie für die Bestellung wartender Produzenten- und Konsumententhreads
  • LinkedBlockingQueue hat normalerweise einen höheren Durchsatz als Array-basierte Warteschlangen, aber in den meisten gleichzeitigen Anwendungen eine weniger vorhersehbare Leistung.

Ich habe zwei Szenarien, eine erfordert, dass die Warteschlange viele Produzenten (Threads, die sie verwenden) mit einem Verbraucher unterstützt, und die andere ist umgekehrt.

Ich verstehe nicht, welche Implementierung ich verwenden soll. Kann jemand erklären, was die Unterschiede sind?

Was ist die "optionale Fairness-Richtlinie" in der ArrayBlockingQueue?

David Hofmann
quelle
1
Sie haben vergessen, auch nach PriorityBlockingQueue zu fragen. Dies ist nützlich, um eine Reihenfolge anzugeben, in der Threads verarbeitet werden.
IgorGanapolsky

Antworten:

53

Grundsätzlich besteht der Unterschied zwischen ihnen in den Leistungsmerkmalen und im Blockierverhalten.

Am einfachsten ArrayBlockingQueueist zunächst eine Warteschlange mit fester Größe. Wenn Sie also die Größe auf 10 setzen und versuchen, ein 11. Element einzufügen, wird die insert-Anweisung blockiert, bis ein anderer Thread ein Element entfernt. Das Fairness-Problem besteht darin, was passiert, wenn mehrere Threads gleichzeitig versuchen, sie einzufügen und zu entfernen (dh während des Zeitraums, in dem die Warteschlange blockiert wurde). Ein Fairness-Algorithmus stellt sicher, dass der erste Thread, der fragt, der erste Thread ist, der abgerufen wird. Andernfalls kann ein bestimmter Thread länger warten als andere Threads, was zu unvorhersehbarem Verhalten führt (manchmal dauert ein Thread nur einige Sekunden, da andere Threads, die später gestartet wurden, zuerst verarbeitet wurden). Der Nachteil ist, dass die Verwaltung der Fairness einen Overhead erfordert und den Durchsatz verlangsamt.

Der wichtigste Unterschied zwischen LinkedBlockingQueueund ConcurrentLinkedQueuebesteht darin, dass LinkedBlockingQueueIhr Thread wartet, bis etwas vorhanden ist, wenn Sie ein Element von a anfordern und die Warteschlange leer ist. A ConcurrentLinkedQueuekehrt sofort mit dem Verhalten einer leeren Warteschlange zurück.

Welches davon abhängt, ob Sie die Blockierung benötigen. Wo Sie viele Produzenten und einen Verbraucher haben, klingt es so. Wenn Sie dagegen viele Verbraucher und nur einen Hersteller haben, benötigen Sie möglicherweise nicht das Blockierungsverhalten und lassen die Verbraucher möglicherweise nur prüfen, ob die Warteschlange leer ist, und fahren fort, wenn dies der Fall ist.

Yishai
quelle
67
Die Antwort ist irreführend. Sowohl LinkedBlockingQueue als auch ConcurrentLinkedQueue haben die Methode "poll ()", die den Kopf der Warteschlange entfernt oder null zurückgibt (nicht blockiert), und die Methode "quote (E e)", die in das Ende der Warteschlange eingefügt wird und nicht blockiert. Der Unterschied besteht darin, dass nur LinkedBlockingQueue zusätzlich zu den nicht blockierenden Operationen Blockierungsvorgänge hat - und für dieses Privileg zahlen Sie den Preis, für den LinkedBlockingQueue tatsächlich Sperren hat. Die andere Antwort erklärt dies.
Nakedible
123

ConcurrentLinkedQueue bedeutet, dass keine Sperren genommen werden (dh keine synchronisierten (dies) oder Lock.lock- Aufrufe). Bei Änderungen wird eine CAS-Compare- und Swap- Operation verwendet, um festzustellen, ob der Head / Tail-Knoten noch derselbe ist wie zu Beginn. Wenn ja, ist die Operation erfolgreich. Wenn der Kopf / Schwanz-Knoten unterschiedlich ist, dreht er sich und versucht es erneut.

LinkedBlockingQueue sperrt vor jeder Änderung. Ihre Angebotsanrufe würden also blockiert, bis sie die Sperre erhalten. Sie können die Angebotsüberladung verwenden, die eine TimeUnit benötigt, um zu sagen, dass Sie nur X Zeit warten möchten, bevor Sie das Hinzufügen abbrechen (normalerweise gut für Warteschlangen vom Nachrichtentyp, bei denen die Nachricht nach X Millisekunden veraltet ist).

Fairness bedeutet, dass die Lock-Implementierung die geordneten Threads beibehält. Das heißt, wenn Thread A und dann Thread B eintreten, erhält Thread A zuerst die Sperre. Ohne Fairness ist es wirklich undefiniert, was passiert. Es wird höchstwahrscheinlich der nächste Thread sein, der geplant wird.

Welche verwendet werden soll, hängt davon ab. Ich neige dazu, ConcurrentLinkedQueue zu verwenden, da meine Produzenten unterschiedliche Zeit benötigen, um Arbeit in die Warteschlange zu stellen. Ich habe nicht viele Produzenten, die genau im selben Moment produzieren. Die Verbraucherseite ist jedoch komplizierter, da die Umfrage nicht in einen guten Schlafzustand übergeht. Damit müssen Sie selbst umgehen.

Justin Rudd
quelle
1
Und unter welchen Bedingungen ist ArrayBlockingQueue besser als LinkedBlockingQueue?
Kolobok
@akapelko ArrayBlockingQueue ermöglicht eine feinere Reihenfolge.
IgorGanapolsky
2
Was bedeutet das? "Es wird sich drehen und es erneut versuchen." ?
Lester
9

In Ihrem Fragentitel wird das Blockieren von Warteschlangen erwähnt. Ist ConcurrentLinkedQueuejedoch keine blockierende Warteschlange.

Die BlockingQueues sind ArrayBlockingQueue, DelayQueue, LinkedBlockingDeque, LinkedBlockingQueue, PriorityBlockingQueue, und SynchronousQueue.

Einige davon sind eindeutig nicht fit für Ihre Zwecke ( DelayQueue, PriorityBlockingQueueund SynchronousQueue). LinkedBlockingQueueund LinkedBlockingDequesind identisch, außer dass letztere eine doppelendige Warteschlange ist (sie implementiert die Deque-Schnittstelle).

Da dies ArrayBlockingQueuenur nützlich ist, wenn Sie die Anzahl der Elemente begrenzen möchten, würde ich mich daran halten LinkedBlockingQueue.

Powerlord
quelle
Ich habe das Sperrwort aus dem Titel entfernt, danke. Lassen Sie mich sehen, ob ich es verstanden habe. Was Sie gesagt haben, bedeutet, dass LinkedBlockingQueue in mehreren Konsumenten verwendet werden kann / Szenarien über dasselbe Objekt erzeugt.
David Hofmann
1
Ich dachte, dass ArrayBlockingQueue eine feinkörnigere Reihenfolge von Threads ermöglicht? Daher sein Vorteil.
IgorGanapolsky
4

ArrayBlockingQueue hat einen geringeren Speicherbedarf und kann Elementknoten wiederverwenden, ähnlich wie LinkedBlockingQueue, die für jede neue Einfügung ein LinkedBlockingQueue $ Node-Objekt erstellen müssen.

Deng
quelle
1
guter Punkt! Ich bevorzuge ArrayBlockingQueue mehr als LinkedBlockingQueue
Billionen
2
Dies ist nicht unbedingt der Fall. Wenn Ihre Warteschlange die meiste Zeit fast leer ist, aber groß ArrayBlockingQueuewerden muss, hat sie einen viel schlechteren Speicherbedarf. Während der gesamten Zeit ist immer noch ein großes Array im Speicher zugeordnet Das LinkedBlockingQueuewird einen vernachlässigbaren Speicherbedarf haben, wenn es fast leer ist.
Krease
1
  1. SynchronousQueue(Aus einer anderen Frage entnommen )

SynchronousQueueist eher eine Übergabe, während das LinkedBlockingQueuenur ein einzelnes Element erlaubt. Der Unterschied besteht darin, dass der put()Anruf an a SynchronousQueueerst bei einem entsprechenden take()Anruf zurückgegeben wird. Bei einem Anruf LinkedBlockingQueueder Größe 1 wird der put()Anruf (an eine leere Warteschlange) jedoch sofort zurückgegeben. Dies ist im Wesentlichen die BlockingQueueImplementierung, wenn Sie nicht wirklich eine Warteschlange möchten (Sie möchten keine ausstehenden Daten verwalten).

  1. LinkedBlockingQueue( LinkedListImplementierung, aber nicht genau JDK-Implementierung LinkedListverwendet den statischen Knoten der inneren Klasse, um Verknüpfungen zwischen Elementen aufrechtzuerhalten.)

Konstruktor für 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 )
}

Knotenklasse Zum Verwalten von Links

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

3 . ArrayBlockingQueue (Array-Implementierung)

Konstruktor für 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 Größter Unterschied zwischen ArrayBlockingQueueund LinkedBlockingQueueist klar aus Konstruktor, dem die zugrunde liegende Datenstruktur Array und andere linkedList zugrunde liegen .

ArrayBlockingQueueverwendet den Single-Lock-Double-Bedingungsalgorithmus und LinkedBlockingQueueist eine Variante des "Two-Lock-Queue" -Algorithmus. Er verfügt über 2 Locks und 2 Condition-Bedingungen (takeLock, putLock).

Vipin
quelle
0

ConcurrentLinkedQueue ist sperrenfrei, LinkedBlockingQueue nicht. Jedes Mal, wenn Sie LinkedBlockingQueue.put () oder LinkedBlockingQueue.take () aufrufen, müssen Sie zuerst die Sperre erwerben. Mit anderen Worten, LinkedBlockingQueue weist eine schlechte Parallelität auf. Wenn Sie Wert auf Leistung legen, versuchen Sie es mit ConcurrentLinkedQueue + LockSupport.

user319609
quelle