Come utilizzare ConcurrentLinkedQueue?


95

Come si usa un ConcurrentLinkedQueuein Java?
Usando questo LinkedQueue, devo essere preoccupato per la concorrenza nella coda? Oppure devo solo definire due metodi (uno per recuperare elementi dalla lista e un altro per aggiungere elementi alla lista)?
Nota: ovviamente questi due metodi devono essere sincronizzati. Destra?


EDIT: Quello che sto cercando di fare è questo: ho una classe (in Java) con un metodo per recuperare gli elementi dalla coda e un'altra classe con un metodo per aggiungere elementi alla coda. Gli elementi aggiunti e recuperati dall'elenco sono oggetti della mia classe.

Un'altra domanda: devo farlo nel metodo di rimozione:

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

Ho solo un consumatore e un produttore.


Grazie per la risposta alla domanda mt. Quello che sto cercando di fare è questo: ho una classe (in Java) con un metodo per recuperare elementi dalla coda e un'altra classe con un metodo per aggiungere elementi alla coda. Gli elementi aggiunti e recuperati dall'elenco sono oggetti della mia classe.
Ricardo Felgueiras

2
Dovresti modificare la tua domanda e inserire questo chiarimento nella domanda stessa.
Adam Jaskiewicz

Risposte:


156

No, i metodi non devono essere sincronizzati e non è necessario definire alcun metodo; sono già in ConcurrentLinkedQueue, basta usarli. ConcurrentLinkedQueue esegue internamente tutte le operazioni di blocco e altre operazioni necessarie; i tuoi produttori aggiungono i dati alla coda e i tuoi consumatori lo chiedono.

Per prima cosa, crea la tua coda:

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

Ora, ovunque tu stia creando i tuoi oggetti produttore / consumatore, passa in coda in modo che abbiano un posto dove mettere i loro oggetti (potresti usare un setter per questo, invece, ma preferisco fare questo genere di cose in un costruttore):

YourProducer producer = new YourProducer(queue);

e:

YourConsumer consumer = new YourConsumer(queue);

e aggiungici qualcosa nel tuo produttore:

queue.offer(myObject);

e prendi le cose nel tuo consumatore (se la coda è vuota, poll () restituirà null, quindi controllalo):

YourObject myObject = queue.poll();

Per maggiori informazioni vedere il Javadoc

MODIFICARE:

Se hai bisogno di bloccare l'attesa che la coda non sia vuota, probabilmente vorrai usare LinkedBlockingQueue e usare il metodo take (). Tuttavia, LinkedBlockingQueue ha una capacità massima (il valore predefinito è Integer.MAX_VALUE, che è superiore a due miliardi) e quindi può o non può essere appropriato a seconda delle circostanze.

Se hai solo un thread che inserisce elementi nella coda e un altro thread che estrae elementi dalla coda, ConcurrentLinkedQueue è probabilmente eccessivo. È più per quando potresti avere centinaia o addirittura migliaia di thread che accedono alla coda contemporaneamente. Le tue esigenze saranno probabilmente soddisfatte utilizzando:

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

Un vantaggio di questo è che si blocca sull'istanza (coda), quindi puoi sincronizzarti sulla coda per garantire l'atomicità delle operazioni composite (come spiegato da Jared). NON È POSSIBILE farlo con ConcurrentLinkedQueue, poiché tutte le operazioni vengono eseguite SENZA blocco sull'istanza (utilizzando le variabili java.util.concurrent.atomic). NON sarà necessario farlo se si desidera bloccare mentre la coda è vuota, perché poll () restituirà semplicemente null mentre la coda è vuota e poll () è atomico. Controlla se poll () restituisce null. In tal caso, aspetta (), quindi riprova. Non c'è bisogno di bloccare.

Finalmente:

Onestamente, userei solo una LinkedBlockingQueue. È ancora eccessivo per la tua applicazione, ma è probabile che funzioni bene. Se non è abbastanza performante (PROFILO!), Puoi sempre provare qualcos'altro, e significa che non devi occuparti di NESSUNA roba sincronizzata:

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.

Tutto il resto è lo stesso. Put probabilmente non bloccherà, perché è improbabile che tu metta due miliardi di oggetti in coda.


Grazie per la risposta. Un'altra domanda: devo farlo nel metodo di rimozione: while (queue.size () == 0) wait (); queue.poll ();
Ricardo Felgueiras

Risponderò come una modifica alla mia risposta, poiché è piuttosto importante.
Adam Jaskiewicz

Poiché ha causato confusione in un'altra domanda, Collection.synchronizedListrestituisce un Listche non implementa Queue.
Tom Hawtin - tackline

@AdamJaskiewicz sta usando ConcurrentLinkedQueueper Producer consumatori una buona idea, mi riferisco a questo post stackoverflow.com/questions/1426754/...
RD22

37

Questo è in gran parte un duplicato di un'altra domanda .

Ecco la sezione di quella risposta che è pertinente a questa domanda:

Devo eseguire la mia sincronizzazione se utilizzo java.util.ConcurrentLinkedQueue?

Le operazioni atomiche sulle raccolte simultanee vengono sincronizzate automaticamente. In altre parole, ogni singola chiamata alla coda è garantita thread-safe senza alcuna azione da parte tua. Ciò che non è garantito thread-safe sono le operazioni eseguite sulla raccolta che non sono atomiche.

Ad esempio, questo è threadsafe senza alcuna azione da parte tua:

queue.add(obj);

o

queue.poll(obj);

Però; le chiamate non atomiche alla coda non sono automaticamente thread-safe. Ad esempio, le seguenti operazioni non sono automaticamente threadsafe:

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

Quest'ultimo non è threadsafe, poiché è molto probabile che tra il momento in cui viene chiamato isEmpty e il momento in cui viene chiamato il poll, altri thread abbiano aggiunto o rimosso elementi dalla coda. Il modo sicuro per eseguire questa operazione è questo:

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

Di nuovo ... le chiamate atomiche alla coda sono automaticamente thread-safe. Le chiamate non atomiche non lo sono.


1
L'unico uso comune di cui posso pensare è bloccare fino a quando la coda non è vuota. Non sarebbe meglio servire utilizzando un'implementazione di BlockingQueue (take () è atomico e si blocca finché non c'è qualcosa da consumare)?
Adam Jaskiewicz

6
Inoltre devi stare attento con quello. A differenza di synchronizedList, ConcurrentLinkedQueue non si sincronizza su se stesso, quindi nel tuo codice sarebbe ancora possibile per un produttore offrire alla coda mentre sei nel tuo blocco sincronizzato.
Adam Jaskiewicz

Perché dovresti combinare queue.isEmpty () e queue.poll ()? Non puoi semplicemente sondare e verificare se il risultato è nullo? (La mia comprensione è che se si interroga una coda vuota restituisce null)
AjahnCharles

Sono d'accordo con tutto nel tuo codice tranne l'ultimo blocco di codice. La sincronizzazione su ConcurrentLInkedQueue non garantisce nulla perché le altre chiamate non vengono sincronizzate. Quindi potrebbe esserci un "add (..)" da un altro thread tra "isEmpty ()" e "poll (...)" anche se hai sincronizzato questo thread
Klitos G.

6

Usa sondaggio per ottenere il primo elemento e aggiungi per aggiungere un nuovo ultimo elemento. Questo è tutto, nessuna sincronizzazione o altro.


6

Questo è probabilmente quello che stai cercando in termini di sicurezza dei thread e "bellezza" quando cerchi di consumare tutto nella coda:

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

Ciò garantirà che esci quando la coda è vuota e che continui a estrarre oggetti da essa finché non è vuota.


Molto utile per svuotare una coda. Grazie.
DevilCode

2

ConcurentLinkedQueue è un'implementazione gratuita di attesa / blocco molto efficiente (vedere javadoc per riferimento), quindi non solo non è necessario sincronizzare, ma la coda non bloccherà nulla, essendo quindi virtualmente veloce come un non sincronizzato (non thread sicuro) uno.


1

Usalo come faresti con una raccolta non concorrente. Le classi Concurrent [Collection] racchiudono le raccolte regolari in modo da non dover pensare alla sincronizzazione dell'accesso.

Modifica: ConcurrentLinkedList non è in realtà solo un wrapper, ma piuttosto una migliore implementazione simultanea. In ogni caso, non devi preoccuparti della sincronizzazione.


ConcurrentLinkedQueue non lo è. È appositamente costruito da zero per l'accesso simultaneo da parte di più produttori e consumatori. Un po 'più elaborato dei semplici wrapper restituiti da Collections.synchronized *
Adam Jaskiewicz

Sì, sono state aggiunte molte cose sulla concorrenza in J2SE 5.
Adam Jaskiewicz
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.