Meine Frage bezieht sich auf diese zuvor gestellte Frage. In Situationen, in denen ich eine Warteschlange für die Kommunikation zwischen Produzenten- und Konsumententhreads verwende, würden die Leute generell empfehlen, LinkedBlockingQueue
oder zu verwenden ConcurrentLinkedQueue
?
Was sind die Vor- und Nachteile einer Verwendung übereinander?
Der Hauptunterschied, den ich aus API-Sicht sehen kann, besteht darin, dass a LinkedBlockingQueue
optional begrenzt werden kann.
ConcurrentLinkedQueue
ist auch nützlich, wenn Ihr Thread mehrere Warteschlangen überprüft. Zum Beispiel in einem Server mit mehreren Mandanten. Angenommen, Sie verwenden aus Isolationsgründen nicht eine einzige Blockierungswarteschlange und keinen Mandantendiskriminator.take()
undput()
lediglich zusätzliche Ressourcen (Synchronisationsinterms) verbrauchen alsConcurrentLinkedQueue
. obwohl es der Fall ist, begrenzte Warteschlangen für Produzenten-Konsumenten-Szenarien zu verwendenDiese Frage verdient eine bessere Antwort.
Java
ConcurrentLinkedQueue
basiert auf dem berühmten Algorithmus von Maged M. Michael und Michael L. Scott für nicht blockierende Warteschlangen ohne Sperren ."Nicht blockierend" als Begriff für eine konkurrierende Ressource (unsere Warteschlange) bedeutet, dass unabhängig davon, was der Scheduler der Plattform tut, wie das Unterbrechen eines Threads oder wenn der betreffende Thread einfach zu langsam ist, andere Threads um dieselbe Ressource konkurrieren wird noch in der Lage sein, Fortschritte zu machen. Wenn es sich beispielsweise um eine Sperre handelt, kann der Thread, der die Sperre hält, unterbrochen werden, und alle Threads, die auf diese Sperre warten, werden blockiert. Intrinsische Sperren (das
synchronized
Schlüsselwort) in Java können auch mit erheblichen Leistungseinbußen verbunden sein - wie beim voreingenommenen Sperrenist beteiligt und Sie haben Konflikte, oder nachdem die VM beschlossen hat, die Sperre nach einer Spin-Grace-Periode zu "aufblasen" und konkurrierende Threads zu blockieren ... weshalb in vielen Kontexten (Szenarien mit geringen / mittleren Konflikten) Vergleiche und -Sätze für atomare Referenzen können viel effizienter sein, und genau das tun viele nicht blockierende Datenstrukturen.Java
ConcurrentLinkedQueue
ist nicht nur nicht blockierend, sondern hat auch die großartige Eigenschaft, dass der Produzent nicht mit dem Verbraucher konkurriert. In einem Einzelproduzenten / Einzelkonsumentenszenario (SPSC) bedeutet dies wirklich, dass es keinen nennenswerten Streit geben wird. In einem Szenario mit mehreren Herstellern / einzelnen Verbrauchern wird der Verbraucher nicht mit den Herstellern konkurrieren. Diese Warteschlange hat Konflikte, wenn mehrere Produzenten dies versuchenoffer()
, aber das ist per Definition Parallelität. Es ist im Grunde eine allgemeine und effiziente nicht blockierende Warteschlange.Das
BlockingQueue
Blockieren eines Threads, um in einer Warteschlange zu warten, ist eine schrecklich schreckliche Art, gleichzeitige Systeme zu entwerfen. Tu es nicht. Wenn Sie nicht herausfinden können, wie einConcurrentLinkedQueue
in einem Consumer / Producer-Szenario verwendet wird, wechseln Sie einfach zu übergeordneten Abstraktionen, wie z. B. einem guten Schauspieler-Framework.quelle
LinkedBlockingQueue
blockiert den Konsumenten oder Produzenten, wenn die Warteschlange leer oder voll ist und der jeweilige Konsumenten- / Produzenten-Thread in den Ruhezustand versetzt wird. Diese Blockierungsfunktion ist jedoch mit Kosten verbunden: Jede Put- oder Take-Operation ist eine Sperre zwischen den Herstellern oder Verbrauchern (wenn viele), sodass in Szenarien mit vielen Herstellern / Verbrauchern die Operation möglicherweise langsamer ist.ConcurrentLinkedQueue
verwendet keine Sperren, sondern CAS für seine Put / Take-Operationen, wodurch möglicherweise die Konkurrenz mit vielen Produzenten- und Konsumententhreads verringert wird. Da es sich jedoch um eine "wartefreie" Datenstruktur handelt,ConcurrentLinkedQueue
wird sie nicht blockiert, wenn sie leer ist. Dies bedeutet, dass der Verbraucher dietake()
zurückgegebenennull
Werte durch "beschäftigtes Warten" verarbeiten muss, z. B. wenn der Verbraucher-Thread die CPU auffrisst.Welches also "besser" ist, hängt von der Anzahl der Consumer-Threads, der Rate, die sie verbrauchen / produzieren usw. ab. Für jedes Szenario wird ein Benchmark benötigt.
Ein besonderer Anwendungsfall, bei dem das
ConcurrentLinkedQueue
eindeutig besser ist, ist, wenn Produzenten zuerst etwas produzieren und ihre Arbeit beenden, indem sie die Arbeit in die Warteschlange stellen, und erst , wenn die Verbraucher anfangen zu konsumieren, in dem Wissen, dass sie erledigt werden, wenn die Warteschlange leer ist. (Hier besteht keine Parallelität zwischen Produzent-Konsument, sondern nur zwischen Produzent-Produzent und Konsument-Konsument)quelle
unbounded blockingqueue
es verwendet wird, wäre es besser als CAS- basiertes ConcurrentConcurrentLinkedQueue
Eine andere Lösung (die sich nicht gut skalieren lässt) sind Rendezvous-Kanäle: java.util.concurrent SynchronousQueue
quelle
Wenn Ihre Warteschlange nicht erweiterbar ist und nur einen Produzenten- / Konsumententhread enthält. Sie können eine sperrenlose Warteschlange verwenden (Sie müssen den Datenzugriff nicht sperren).
quelle