LinkedBlockingQueue vs ConcurrentLinkedQueue

1. Einleitung

LinkedBlockingQueue und ConcurrentLinkedQueue sind die beiden am häufigsten verwendeten gleichzeitigen Warteschlangen in Java . Obwohl beide Warteschlangen häufig als gleichzeitige Datenstruktur verwendet werden, gibt es subtile Merkmale und Verhaltensunterschiede zwischen ihnen.

In diesem kurzen Tutorial werden diese beiden Warteschlangen erläutert und ihre Ähnlichkeiten und Unterschiede erläutert.

2. LinkedBlockingQueue

Die LinkedBlockingQueue ist eine optional begrenzte Blockierungswarteschlangenimplementierung. Dies bedeutet, dass die Warteschlangengröße bei Bedarf angegeben werden kann.

Erstellen wir eine LinkedBlockingQueue, die bis zu 100 Elemente enthalten kann:

BlockingQueue boundedQueue = new LinkedBlockingQueue(100);

Wir können auch eine unbegrenzte LinkedBlockingQueue erstellen, indem wir die Größe nicht angeben :

BlockingQueue unboundedQueue = new LinkedBlockingQueue();

Eine unbegrenzte Warteschlange bedeutet, dass die Größe der Warteschlange beim Erstellen nicht angegeben wird. Daher kann die Warteschlange dynamisch wachsen, wenn Elemente hinzugefügt werden. Wenn jedoch kein Speicher mehr vorhanden ist, löst die Warteschlange einen java.lang.OutOfMemoryError aus.

Wir können eine LinkedBlockingQueue auch aus einer vorhandenen Sammlung erstellen :

Collection listOfNumbers = Arrays.asList(1,2,3,4,5); BlockingQueue queue = new LinkedBlockingQueue(listOfNumbers);

Die LinkedBlockingQueue- Klasse implementiert die BlockingQueue- Schnittstelle, die die Blockierungsnatur bereitstellt .

Eine blockierende Warteschlange zeigt an, dass die Warteschlange den Zugriffsthread blockiert, wenn er voll ist (wenn die Warteschlange begrenzt ist) oder leer wird. Wenn die Warteschlange voll ist, blockiert das Hinzufügen eines neuen Elements den Zugriffsthread, es sei denn, für das neue Element ist Platz verfügbar. Wenn die Warteschlange leer ist, blockiert der Zugriff auf ein Element den aufrufenden Thread:

ExecutorService executorService = Executors.newFixedThreadPool(1); LinkedBlockingQueue queue = new LinkedBlockingQueue(); executorService.submit(() -> { try { queue.take(); } catch (InterruptedException e) { // exception handling } });

Im obigen Code-Snippet greifen wir auf eine leere Warteschlange zu. Daher blockiert die Methode take den aufrufenden Thread.

Die Blockierungsfunktion der LinkedBlockingQueue ist mit einigen Kosten verbunden. Diese Kosten sind darauf zurückzuführen, dass jeder Put- oder Take- Vorgang zwischen dem Produzenten- oder dem Konsumenten-Thread gesperrt ist. In Szenarien mit vielen Herstellern und Verbrauchern kann das Setzen und Ergreifen von Maßnahmen daher langsamer sein.

3. ConcurrentLinkedQueue

Eine ConcurrentLinkedQueue ist eine unbegrenzte, threadsichere und nicht blockierende Warteschlange.

Erstellen wir eine leere ConcurrentLinkedQueue :

ConcurrentLinkedQueue queue = new ConcurrentLinkedQueue();

Wir können eine ConcurrentLinkedQueue auch aus einer vorhandenen Sammlung erstellen :

Collection listOfNumbers = Arrays.asList(1,2,3,4,5); ConcurrentLinkedQueue queue = new ConcurrentLinkedQueue(listOfNumbers);

Im Gegensatz zu einer LinkedBlockingQueue ist eine ConcurrentLinkedQueue eine nicht blockierende Warteschlange . Daher wird ein Thread nicht blockiert, sobald die Warteschlange leer ist. Stattdessen wird null zurückgegeben . Da es unbegrenzt ist, wird ein java.lang.OutOfMemoryError ausgelöst, wenn kein zusätzlicher Speicher zum Hinzufügen neuer Elemente vorhanden ist.

ConcurrentLinkedQueue ist nicht nur nicht blockierend, sondern verfügt auch über zusätzliche Funktionen.

In jedem Erzeuger-Verbraucher-Szenario geben sich die Verbraucher nicht mit den Erzeugern zufrieden. Es werden jedoch mehrere Hersteller miteinander kämpfen:

int element = 1; ExecutorService executorService = Executors.newFixedThreadPool(2); ConcurrentLinkedQueue queue = new ConcurrentLinkedQueue(); Runnable offerTask = () -> queue.offer(element); Callable pollTask = () -> { while (queue.peek() != null) { return queue.poll().intValue(); } return null; }; executorService.submit(offerTask); Future returnedElement = executorService.submit(pollTask); assertThat(returnedElement.get().intValue(), is(equalTo(element))); 

Die erste Aufgabe, quoteTask , fügt der Warteschlange ein Element hinzu, und die zweite Aufgabe, pollTask, ruft ein Element aus der Warteschlange ab. Die Abfrageaufgabe überprüft zusätzlich zuerst die Warteschlange auf ein Element, da ConcurrentLinkedQueue nicht blockiert und einen Nullwert zurückgeben kann .

4. Ähnlichkeiten

Sowohl LinkedBlockingQueue als auch ConcurrentLinkedQueue sind Warteschlangenimplementierungen und weisen einige gemeinsame Merkmale auf. Lassen Sie uns die Ähnlichkeiten dieser beiden Warteschlangen diskutieren:

  1. Beide Geräte die Queue - Schnittstelle
  2. Beide verwenden verknüpfte Knoten , um ihre Elemente zu speichern
  3. Beide eignen sich für Szenarien mit gleichzeitigem Zugriff

5. Unterschiede

Obwohl diese beiden Warteschlangen bestimmte Ähnlichkeiten aufweisen, gibt es auch erhebliche Unterschiede in den Merkmalen:

Merkmal LinkedBlockingQueue ConcurrentLinkedQueue
Natur blockieren Es ist eine blockierende Warteschlange und implementiert die BlockingQueue- Schnittstelle Es ist eine nicht blockierende Warteschlange und implementiert die BlockingQueue- Schnittstelle nicht
Warteschlangengröße Es handelt sich um eine optional begrenzte Warteschlange, dh es gibt Bestimmungen zum Definieren der Warteschlangengröße während der Erstellung Es handelt sich um eine unbegrenzte Warteschlange, und es ist nicht vorgesehen, die Warteschlangengröße während der Erstellung anzugeben
Die Natur sperren Es ist eine sperrbasierte Warteschlange Es ist eine Warteschlange ohne Sperren
Algorithmus Es implementiert seine Verriegelungs basierend auf zwei Verriegelungswarteschlange Algorithmus Es basiert auf dem Michael & Scott-Algorithmus für nicht blockierende, sperrfreie Warteschlangen
Implementierung In dem Zwei-Verriegelungswarteschlange Algorithmus Mechanismus, LinkedBlockingQueue verwendet zwei unterschiedliche Schlösser - die putLock und die takeLock . Die Put / Take- Operationen verwenden den ersten Sperrtyp und die Take / Poll- Operationen verwenden den anderen Sperrtyp Es verwendet CAS (Compare-And-Swap ) für seine Operationen
Blockierungsverhalten Es ist eine blockierende Warteschlange. Es blockiert also die zugreifenden Threads, wenn die Warteschlange leer ist Der Zugriffsthread wird nicht blockiert, wenn die Warteschlange leer ist und null zurückgibt

6. Fazit

In diesem Artikel haben wir etwas über LinkedBlockingQueue und ConcurrentLinkedQueue erfahren .

Zunächst haben wir diese beiden Warteschlangenimplementierungen und einige ihrer Merkmale einzeln erörtert. Dann haben wir die Ähnlichkeiten zwischen diesen beiden Warteschlangenimplementierungen gesehen. Schließlich haben wir die Unterschiede zwischen diesen beiden Warteschlangenimplementierungen untersucht.

Wie immer ist der Quellcode der Beispiele auf GitHub verfügbar.