Wie verwende ich ConcurrentLinkedQueue?

95

Wie verwende ich ein ConcurrentLinkedQueuein Java?
Muss LinkedQueueich mir dabei Sorgen um die Parallelität in der Warteschlange machen? Oder muss ich nur zwei Methoden definieren (eine zum Abrufen von Elementen aus der Liste und eine zum Hinzufügen von Elementen zur Liste)?
Hinweis: Natürlich müssen diese beiden Methoden synchronisiert werden. Richtig?


BEARBEITEN: Ich versuche Folgendes zu tun: Ich habe eine Klasse (in Java) mit einer Methode zum Abrufen von Elementen aus der Warteschlange und eine andere Klasse mit einer Methode zum Hinzufügen von Elementen zur Warteschlange. Die Elemente, die hinzugefügt und aus der Liste abgerufen werden, sind Objekte meiner eigenen Klasse.

Noch eine Frage: Muss ich dies in der Methode remove tun?

while (queue.size() == 0){ 
  wait(); 
  queue.poll();
}

Ich habe nur einen Verbraucher und einen Produzenten.

Ricardo Felgueiras
quelle
Vielen Dank für die Antwort auf mt Frage. Ich versuche Folgendes zu tun: Ich habe eine Klasse (in Java) mit einer Methode zum Abrufen von Elementen aus der Warteschlange und eine andere Klasse mit einer Methode zum Hinzufügen von Elementen zur Warteschlange. Die Elemente, die hinzugefügt und aus der Liste abgerufen werden, sind Objekte meiner eigenen Klasse.
Ricardo Felgueiras
2
Sie sollten Ihre Frage bearbeiten und diese Klarstellung in die Frage selbst einfügen.
Adam Jaskiewicz

Antworten:

156

Nein, die Methoden müssen nicht synchronisiert werden, und Sie müssen keine Methoden definieren. Sie befinden sich bereits in ConcurrentLinkedQueue. Verwenden Sie sie einfach. ConcurrentLinkedQueue führt alle Sperr- und sonstigen Operationen aus, die Sie intern benötigen. Ihre Produzenten fügen Daten in die Warteschlange ein und Ihre Konsumenten fragen danach ab.

Erstellen Sie zunächst Ihre Warteschlange:

Queue<YourObject> queue = new ConcurrentLinkedQueue<YourObject>();

Übergeben Sie nun, wo immer Sie Ihre Produzenten- / Konsumentenobjekte erstellen, die Warteschlange, damit sie irgendwo ihre Objekte ablegen können (Sie könnten stattdessen einen Setter dafür verwenden, aber ich bevorzuge es, solche Dinge in einem Konstruktor zu tun):

YourProducer producer = new YourProducer(queue);

und:

YourConsumer consumer = new YourConsumer(queue);

und füge Sachen in deinem Produzenten hinzu:

queue.offer(myObject);

und nehmen Sie Sachen in Ihrem Verbraucher heraus (wenn die Warteschlange leer ist, gibt poll () null zurück, also überprüfen Sie es):

YourObject myObject = queue.poll();

Weitere Informationen finden Sie im Javadoc

BEARBEITEN:

Wenn Sie das Warten blockieren müssen, bis die Warteschlange nicht leer ist, möchten Sie wahrscheinlich eine LinkedBlockingQueue verwenden und die Methode take () verwenden. LinkedBlockingQueue hat jedoch eine maximale Kapazität (standardmäßig Integer.MAX_VALUE, die über zwei Milliarden beträgt) und kann daher je nach Ihren Umständen angemessen sein oder auch nicht.

Wenn nur ein Thread Inhalte in die Warteschlange stellt und ein anderer Thread Inhalte aus der Warteschlange entfernt, ist ConcurrentLinkedQueue wahrscheinlich übertrieben. Dies ist eher der Fall, wenn Hunderte oder sogar Tausende von Threads gleichzeitig auf die Warteschlange zugreifen. Ihre Bedürfnisse werden wahrscheinlich erfüllt durch:

Queue<YourObject> queue = Collections.synchronizedList(new LinkedList<YourObject>());

Ein Plus davon ist, dass die Instanz (Warteschlange) gesperrt wird, sodass Sie in der Warteschlange synchronisieren können, um die Atomizität zusammengesetzter Operationen sicherzustellen (wie von Jared erläutert). Sie können dies NICHT mit einer ConcurrentLinkedQueue tun, da alle Vorgänge OHNE Sperren der Instanz ausgeführt werden (unter Verwendung der Variablen java.util.concurrent.atomic). Sie müssen dies NICHT tun, wenn Sie blockieren möchten, während die Warteschlange leer ist, da poll () einfach null zurückgibt, während die Warteschlange leer ist, und poll () atomar ist. Überprüfen Sie, ob poll () null zurückgibt. Wenn dies der Fall ist, warten Sie () und versuchen Sie es erneut. Keine Notwendigkeit zu sperren.

Schließlich:

Ehrlich gesagt würde ich nur eine LinkedBlockingQueue verwenden. Es ist immer noch übertrieben für Ihre Anwendung, aber die Chancen stehen gut, dass es gut funktionieren wird. Wenn es nicht performant genug ist (PROFIL!), Können Sie immer etwas anderes ausprobieren, und es bedeutet, dass Sie sich nicht mit synchronisierten Dingen befassen müssen:

BlockingQueue<YourObject> queue = new LinkedBlockingQueue<YourObject>();

queue.put(myObject); // Blocks until queue isn't full.

YourObject myObject = queue.take(); // Blocks until queue isn't empty.

Alles andere ist das gleiche. Put wird wahrscheinlich nicht blockiert, da Sie wahrscheinlich nicht zwei Milliarden Objekte in die Warteschlange stellen.

Adam Jaskiewicz
quelle
Vielen Dank für Ihre Antwort. Noch eine Frage: Muss ich dies in der Methode remove tun: while (queue.size () == 0) wait (); queue.poll ();
Ricardo Felgueiras
Ich werde das als Bearbeitung meiner Antwort beantworten, da es ziemlich wichtig ist.
Adam Jaskiewicz
Wie in einer anderen Frage Verwirrung verursacht, wird Collection.synchronizedListeine zurückgegeben, Listdie nicht implementiert wird Queue.
Tom Hawtin - Tackline
@AdamJaskiewicz verwendet ConcurrentLinkedQueuefür Producer Consumer eine gute Idee, ich beziehe mich auf diesen Beitrag stackoverflow.com/questions/1426754/…
rd22
37

Dies ist größtenteils ein Duplikat einer anderen Frage .

Hier ist der Abschnitt dieser Antwort, der für diese Frage relevant ist:

Muss ich meine eigene Synchronisation durchführen, wenn ich java.util.ConcurrentLinkedQueue verwende?

Atomare Operationen für die gleichzeitigen Sammlungen werden für Sie synchronisiert. Mit anderen Worten, jeder einzelne Aufruf der Warteschlange ist garantiert threadsicher, ohne dass Sie etwas unternehmen müssen. Was nicht garantiert threadsicher ist, sind alle Operationen, die Sie an der Sammlung ausführen und die nicht atomar sind.

Dies ist beispielsweise threadsicher, ohne dass Sie etwas unternehmen müssen:

queue.add(obj);

oder

queue.poll(obj);

Jedoch; Nicht-atomare Aufrufe der Warteschlange sind nicht automatisch threadsicher. Beispielsweise sind die folgenden Vorgänge nicht automatisch threadsicher:

if(!queue.isEmpty()) {
   queue.poll(obj);
}

Letzteres ist nicht threadsicher, da es sehr wahrscheinlich ist, dass zwischen dem Aufruf von isEmpty und dem Aufruf der Zeitabfrage andere Threads Elemente zur Warteschlange hinzugefügt oder daraus entfernt haben. Der threadsichere Weg, dies durchzuführen, ist folgender:

synchronized(queue) {
    if(!queue.isEmpty()) {
       queue.poll(obj);
    }
}

Wieder ... atomare Aufrufe der Warteschlange sind automatisch threadsicher. Nichtatomare Anrufe sind es nicht.

Jared
quelle
1
Die einzige übliche Verwendung, die ich tun kann, ist das Blockieren, bis die Warteschlange nicht mehr leer ist. Wäre das nicht besser, wenn Sie eine Implementierung von BlockingQueue verwenden (take () ist atomar und blockiert, bis etwas zu konsumieren ist)?
Adam Jaskiewicz
6
Auch damit müssen Sie vorsichtig sein. Im Gegensatz zu synchronizedList wird ConcurrentLinkedQueue nicht für sich selbst synchronisiert, sodass ein Produzent in Ihrem Code der Warteschlange weiterhin anbieten kann, während Sie sich in Ihrem synchronisierten Block befinden.
Adam Jaskiewicz
Warum sollten Sie queue.isEmpty () und queue.poll () kombinieren? Können Sie nicht einfach abfragen und prüfen, ob das Ergebnis null ist? (Mein Verständnis ist, dass, wenn Sie eine leere Warteschlange abfragen, es null zurückgibt)
AjahnCharles
Ich bin mit allem in Ihrem Code einverstanden, mit Ausnahme des letzten Codeblocks. Die Synchronisierung in der ConcurrentLInkedQueue garantiert Ihnen nichts, da andere Aufrufe nicht synchronisiert werden. Es könnte also ein "add (..)" von einem anderen Thread zwischen "isEmpty ()" und "poll (...)"
passieren,
6

Verwenden Sie poll , um das erste Element abzurufen, und add , um ein neues letztes Element hinzuzufügen. Das war's, keine Synchronisation oder irgendetwas anderes.

Hank Gay
quelle
6

Dies ist wahrscheinlich das, wonach Sie in Bezug auf Thread-Sicherheit und "Hübschheit" suchen, wenn Sie versuchen, alles in der Warteschlange zu verbrauchen:

for (YourObject obj = queue.poll(); obj != null; obj = queue.poll()) {
}

Dies garantiert, dass Sie beenden, wenn die Warteschlange leer ist, und dass Sie weiterhin Objekte aus der Warteschlange entfernen, solange sie nicht leer ist.

marq
quelle
Sehr nützlich zum Leeren einer Warteschlange. Vielen Dank.
DevilCode
2

Die ConcurentLinkedQueue ist eine sehr effiziente Implementierung ohne Warten / Sperren (siehe Javadoc als Referenz). Sie müssen also nicht nur nicht synchronisieren, sondern die Warteschlange sperrt auch nichts und ist somit praktisch so schnell wie eine nicht synchronisierte (kein Thread) sicher) eins.

Siddhadev
quelle
1

Verwenden Sie es einfach wie eine nicht gleichzeitige Sammlung. Die Concurrent [Collection] -Klassen umschließen die regulären Sammlungen, sodass Sie nicht über die Synchronisierung des Zugriffs nachdenken müssen.

Bearbeiten: ConcurrentLinkedList ist nicht nur ein Wrapper, sondern eine bessere gleichzeitige Implementierung. In beiden Fällen müssen Sie sich keine Gedanken über die Synchronisierung machen.

Ben S.
quelle
ConcurrentLinkedQueue ist nicht. Es wurde speziell von Grund auf für den gleichzeitigen Zugriff mehrerer Hersteller und Verbraucher entwickelt. Ein bisschen schicker als die einfachen Wrapper, die von Collections.synchronized *
Adam Jaskiewicz am
Ja, in J2SE 5 wurden viele nette Parallelitäts-Dinge hinzugefügt.
Adam Jaskiewicz