¿Cómo utilizar ConcurrentLinkedQueue?

95

¿Cómo uso un ConcurrentLinkedQueueen Java?
Usando esto LinkedQueue, ¿debo preocuparme por la concurrencia en la cola? ¿O simplemente tengo que definir dos métodos (uno para recuperar elementos de la lista y otro para agregar elementos a la lista)?
Nota: obviamente, estos dos métodos deben estar sincronizados. ¿Correcto?


EDITAR: Lo que estoy tratando de hacer es esto: tengo una clase (en Java) con un método para recuperar elementos de la cola y otra clase con un método para agregar elementos a la cola. Los elementos agregados y recuperados de la lista son objetos de mi propia clase.

Una pregunta más: ¿necesito hacer esto en el método de eliminación?

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

Solo tengo un consumidor y un productor.

Ricardo Felgueiras
fuente
Gracias por la respuesta a la pregunta de mt. Lo que estoy tratando de hacer es esto: tengo una clase (en Java) con un método para recuperar elementos de la cola y otra clase con un método para agregar elementos a la cola. Los elementos agregados y recuperados de la lista son objetos de mi propia clase.
Ricardo Felgueiras
2
Debe editar su pregunta y poner esta aclaración en la pregunta misma.
Adam Jaskiewicz

Respuestas:

156

No, no es necesario sincronizar los métodos y no es necesario definir ningún método; ya están en ConcurrentLinkedQueue, solo utilícelos. ConcurrentLinkedQueue realiza todas las operaciones de bloqueo y otras operaciones que necesita internamente; su (s) productor (es) agregan datos a la cola, y sus consumidores los sondean.

Primero, crea tu cola:

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

Ahora, donde sea que esté creando sus objetos de productor / consumidor, pase la cola para que tengan un lugar donde poner sus objetos (podría usar un establecedor para esto, pero prefiero hacer este tipo de cosas en un constructor):

YourProducer producer = new YourProducer(queue);

y:

YourConsumer consumer = new YourConsumer(queue);

y agregarle cosas en su productor:

queue.offer(myObject);

y sacar cosas en su consumidor (si la cola está vacía, poll () devolverá nulo, así que verifíquelo):

YourObject myObject = queue.poll();

Para obtener más información, consulte el Javadoc

EDITAR:

Si necesita bloquear la espera de que la cola no esté vacía, probablemente desee utilizar un LinkedBlockingQueue y usar el método take (). Sin embargo, LinkedBlockingQueue tiene una capacidad máxima (el valor predeterminado es Integer.MAX_VALUE, que es más de dos mil millones) y, por lo tanto, puede o no ser apropiado según sus circunstancias.

Si solo tiene un hilo que pone cosas en la cola y otro hilo que saca cosas de la cola, ConcurrentLinkedQueue probablemente sea excesivo. Es más para cuando puede tener cientos o incluso miles de subprocesos accediendo a la cola al mismo tiempo. Es probable que sus necesidades se satisfagan utilizando:

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

Una ventaja de esto es que se bloquea en la instancia (cola), por lo que puede sincronizar en la cola para garantizar la atomicidad de las operaciones compuestas (como lo explicó Jared). NO PUEDE hacer esto con un ConcurrentLinkedQueue, ya que todas las operaciones se realizan SIN bloquear la instancia (usando variables java.util.concurrent.atomic). NO necesitará hacer esto si desea bloquear mientras la cola está vacía, porque poll () simplemente devolverá nulo mientras la cola está vacía, y poll () es atómico. Compruebe si poll () devuelve nulo. Si es así, espere () y vuelva a intentarlo. No es necesario bloquear.

Finalmente:

Honestamente, solo usaría un LinkedBlockingQueue. Todavía es excesivo para su aplicación, pero es probable que funcione bien. Si no tiene el rendimiento suficiente (¡PERFIL!), Siempre puede probar algo más, y significa que no tiene que lidiar con NINGÚN elemento sincronizado:

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.

Todo lo demás es lo mismo. Put probablemente no se bloqueará, porque no es probable que coloque dos mil millones de objetos en la cola.

Adam Jaskiewicz
fuente
Gracias por su respuesta. Una pregunta más: ¿necesito hacer esto en el método de eliminación? While (queue.size () == 0) wait (); queue.poll ();
Ricardo Felgueiras
Responderé eso como una edición de mi respuesta, ya que es bastante importante.
Adam Jaskiewicz
Como causó confusión en otra pregunta, Collection.synchronizedListdevuelve un Listque no se implementa Queue.
Tom Hawtin - tackline
@AdamJaskiewicz está usando ConcurrentLinkedQueuepara Producer Consumer una buena idea, me refiero a esta publicación stackoverflow.com/questions/1426754/…
rd22
37

Esto es en gran parte un duplicado de otra pregunta. .

Aquí está la sección de esa respuesta que es relevante para esta pregunta:

¿Necesito hacer mi propia sincronización si uso java.util.ConcurrentLinkedQueue?

Las operaciones atómicas en las colecciones concurrentes se sincronizan automáticamente. En otras palabras, cada llamada individual a la cola está garantizada como segura para subprocesos sin ninguna acción de su parte. Lo que no se garantiza que sea seguro para subprocesos son las operaciones que realice en la colección que no sean atómicas.

Por ejemplo, esto es seguro para subprocesos sin ninguna acción de su parte:

queue.add(obj);

o

queue.poll(obj);

Sin embargo; Las llamadas no atómicas a la cola no son automáticamente seguras para subprocesos. Por ejemplo, las siguientes operaciones no son seguras para subprocesos automáticamente:

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

Este último no es seguro para subprocesos, ya que es muy posible que entre el momento en que se llama a isEmpty y el momento en que se llama a la encuesta, otros subprocesos hayan agregado o eliminado elementos de la cola. La forma segura de realizar esto es así:

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

Nuevamente ... las llamadas atómicas a la cola son automáticamente seguras para subprocesos. Las llamadas no atómicas no lo son.

Jared
fuente
1
El único uso común que puedo hacer es bloquear hasta que la cola no esté vacía. ¿No sería mejor utilizar una implementación de BlockingQueue (take () es atómico y bloquea hasta que haya algo para consumir)?
Adam Jaskiewicz
6
También debes tener cuidado con eso. A diferencia de synchronizedList, ConcurrentLinkedQueue no se sincroniza en sí mismo, por lo que en su código aún sería posible que un productor ofrezca a la cola mientras está en su bloque sincronizado.
Adam Jaskiewicz
¿Por qué combinarías queue.isEmpty () y queue.poll ()? ¿No puedes sondear y comprobar si el resultado es nulo? (Tengo entendido que si
sondea
Estoy de acuerdo con todo en su código excepto el último bloque de código. Sincronizar en ConcurrentLInkedQueue no le garantiza nada porque otras llamadas no están sincronizadas. Entonces, podría haber un "agregar (..)" sucediendo desde otro hilo entre su "isEmpty ()" y "poll (...)" aunque haya sincronizado este hilo
Klitos G.
6

Use poll para obtener el primer elemento y agregue para agregar un nuevo último elemento. Eso es todo, sin sincronización ni nada más.

Hank Gay
fuente
6

Esto es probablemente lo que está buscando en términos de seguridad de subprocesos y "belleza" cuando intenta consumir todo en la cola:

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

Esto garantizará que salga cuando la cola esté vacía y que continúe sacando objetos de ella siempre que no esté vacía.

marq
fuente
Muy útil para vaciar una cola. Gracias.
DevilCode
2

ConcurentLinkedQueue es una implementación sin espera / bloqueo muy eficiente (consulte el javadoc como referencia), por lo que no solo no necesita sincronizar, sino que la cola no bloqueará nada, por lo que es virtualmente tan rápido como un no sincronizado (no hilo seguro) uno.

Siddhadev
fuente
1

Úselo como lo haría con una colección no concurrente. Las clases Concurrent [Collection] envuelven las colecciones regulares para que no tenga que pensar en sincronizar el acceso.

Editar: ConcurrentLinkedList no es en realidad solo un contenedor, sino una mejor implementación concurrente. De cualquier manera, no tiene que preocuparse por la sincronización.

Ben S
fuente
ConcurrentLinkedQueue no lo es. Está construido específicamente desde cero para el acceso simultáneo de múltiples productores y consumidores. Un poco más elegante que los contenedores simples devueltos por Collections.synchronized *
Adam Jaskiewicz
Sí, se agregaron muchas cosas
interesantes