Como usar o ConcurrentLinkedQueue?

95

Como faço para usar um ConcurrentLinkedQueueem Java?
Usando isso LinkedQueue, preciso me preocupar com a simultaneidade na fila? Ou eu apenas tenho que definir dois métodos (um para recuperar elementos da lista e outro para adicionar elementos à lista)?
Nota: obviamente, esses dois métodos devem ser sincronizados. Certo?


EDIT: O que estou tentando fazer é o seguinte: Eu tenho uma classe (em Java) com um método para recuperar itens da fila e outra classe com um método para adicionar itens à fila. Os itens adicionados e recuperados da lista são objetos de minha própria classe.

Mais uma pergunta: preciso fazer isso no método de remoção:

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

Tenho apenas um consumidor e um produtor.

Ricardo Felgueiras
fonte
Obrigado pela resposta à pergunta mt. O que estou tentando fazer é o seguinte: tenho uma classe (em Java) com um método para recuperar itens da fila e outra classe com um método para adicionar itens à fila. Os itens adicionados e recuperados da lista são objetos de minha própria classe.
Ricardo Felgueiras
2
Você deve editar sua pergunta e colocar esse esclarecimento na própria pergunta.
Adam Jaskiewicz

Respostas:

156

Não, os métodos não precisam ser sincronizados e você não precisa definir nenhum método; eles já estão em ConcurrentLinkedQueue, basta usá-los. ConcurrentLinkedQueue faz todo o bloqueio e outras operações de que você precisa internamente; o (s) seu (s) produtor (es) adiciona (m) dados à fila e seus consumidores fazem pesquisas para isso.

Primeiro, crie sua fila:

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

Agora, onde quer que você esteja criando seus objetos produtor / consumidor, passe na fila para que eles tenham um lugar para colocar seus objetos (você poderia usar um setter para isso, mas eu prefiro fazer esse tipo de coisa em um construtor):

YourProducer producer = new YourProducer(queue);

e:

YourConsumer consumer = new YourConsumer(queue);

e adicione coisas a ele em seu produtor:

queue.offer(myObject);

e retire as coisas em seu consumidor (se a fila estiver vazia, poll () retornará nulo, então verifique):

YourObject myObject = queue.poll();

Para mais informações veja o Javadoc

EDITAR:

Se você precisa bloquear a espera para que a fila não fique vazia, você provavelmente deseja usar um LinkedBlockingQueue e usar o método take (). No entanto, LinkedBlockingQueue tem uma capacidade máxima (o padrão é Integer.MAX_VALUE, que é mais de dois bilhões) e, portanto, pode ou não ser apropriado dependendo das circunstâncias.

Se você tiver apenas um thread colocando coisas na fila e outro thread retirando coisas da fila, ConcurrentLinkedQueue provavelmente é um exagero. É mais para quando você pode ter centenas ou mesmo milhares de threads acessando a fila ao mesmo tempo. Provavelmente, suas necessidades serão atendidas usando:

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

Uma vantagem disso é que ele bloqueia na instância (fila), para que você possa sincronizar na fila para garantir a atomicidade das operações compostas (conforme explicado por Jared). Você NÃO PODE fazer isso com um ConcurrentLinkedQueue, pois todas as operações são feitas SEM bloqueio na instância (usando variáveis ​​java.util.concurrent.atomic). Você NÃO precisará fazer isso se quiser bloquear enquanto a fila estiver vazia, porque poll () simplesmente retornará nulo enquanto a fila estiver vazia, e poll () é atômico. Verifique se poll () retorna nulo. Se isso acontecer, espere () e tente novamente. Não há necessidade de bloquear.

Finalmente:

Honestamente, eu apenas usaria um LinkedBlockingQueue. Ainda é um exagero para o seu aplicativo, mas provavelmente funcionará bem. Se não tiver desempenho suficiente (PERFIL!), Você sempre pode tentar outra coisa, e isso significa que você não precisa lidar com NENHUM material 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.

Tudo o resto é o mesmo. Put provavelmente não bloqueará, porque você provavelmente não colocará dois bilhões de objetos na fila.

Adam Jaskiewicz
fonte
Obrigado pela sua resposta. Mais uma pergunta: preciso fazer isso no método remove: while (queue.size () == 0) wait (); queue.poll ();
Ricardo Felgueiras
Vou responder isso como uma edição da minha resposta, já que é muito importante.
Adam Jaskiewicz
Como causou confusão em outra questão, Collection.synchronizedListretorna um Listque não implementa Queue.
Tom Hawtin - tackline
@AdamJaskiewicz está usando ConcurrentLinkedQueuepara Produtor Consumidor uma boa ideia, estou me referindo a esta postagem stackoverflow.com/questions/1426754/…
rd22
37

Esta é em grande parte uma duplicata de outra pergunta .

Esta é a seção da resposta que é relevante para esta pergunta:

Preciso fazer minha própria sincronização se usar java.util.ConcurrentLinkedQueue?

As operações atômicas nas coleções simultâneas são sincronizadas para você. Em outras palavras, cada chamada individual para a fila é garantida com thread-safe, sem qualquer ação de sua parte. O que não é garantido como thread-safe são quaisquer operações que você executa na coleção que não são atômicas.

Por exemplo, isso é threadsafe sem qualquer ação de sua parte:

queue.add(obj);

ou

queue.poll(obj);

Contudo; chamadas não atômicas para a fila não são automaticamente thread-safe. Por exemplo, as seguintes operações não são automaticamente threadsafe:

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

Este último não é thread-safe, pois é muito possível que entre o momento em que isEmpty é chamado e o momento em que a enquete é chamada, outros threads tenham adicionado ou removido itens da fila. A maneira threadsafe de fazer isso é assim:

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

Novamente ... chamadas atômicas para a fila são automaticamente thread-safe. Chamadas não atômicas não são.

Jared
fonte
1
O único uso comum que consigo pensar é em bloquear até que a fila não fique vazia. Isso não seria melhor servido usando uma implementação de BlockingQueue (take () é atômico e bloqueia até que haja algo para consumir)?
Adam Jaskiewicz
6
Além disso, você precisa ter cuidado com isso. Ao contrário de synchronizedList, ConcurrentLinkedQueue não sincroniza em si mesmo, então em seu código ainda seria possível para um produtor oferecer para a fila enquanto você está em seu bloco sincronizado.
Adam Jaskiewicz
Por que você combinaria queue.isEmpty () e queue.poll ()? Você não pode apenas pesquisar e verificar se o resultado é nulo? (meu entendimento é que se você pesquisar uma fila vazia, ela retornará nulo)
AjahnCharles
Eu concordo com tudo em seu código, exceto o último bloco de código. A sincronização no ConcurrentLInkedQueue não garante nada, porque outras chamadas não são sincronizadas. Portanto, pode haver um "add (..)" acontecendo em outro tópico entre seu "isEmpty ()" e "enquete (...)", embora você tenha sincronizado este tópico
Klitos G.
6

Use poll para obter o primeiro elemento e add para adicionar um novo último elemento. É isso, sem sincronização ou qualquer outra coisa.

Hank Gay
fonte
6

Isso é provavelmente o que você está procurando em termos de segurança de thread e "beleza" ao tentar consumir tudo na fila:

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

Isso garantirá que você feche quando a fila estiver vazia e que continue a retirar objetos dela, desde que não esteja vazia.

marq
fonte
Muito útil para esvaziar uma fila. Obrigado.
DevilCode de
2

O ConcurentLinkedQueue é uma implementação livre de espera / bloqueio muito eficiente (consulte o javadoc para referência), então não só você não precisa sincronizar, mas a fila não bloqueará nada, sendo virtualmente tão rápida quanto uma não sincronizada (não thread seguro) um.

Sidhadev
fonte
1

Basta usá-lo como faria com uma coleção não simultânea. As classes Concurrent [Collection] envolvem as coleções regulares para que você não precise se preocupar em sincronizar o acesso.

Edit: ConcurrentLinkedList não é apenas um invólucro, mas uma implementação simultânea melhor. De qualquer forma, você não precisa se preocupar com a sincronização.

Ben S
fonte
ConcurrentLinkedQueue não é. Ele é construído especificamente do zero para acesso simultâneo por vários produtores e consumidores. Um pouco mais sofisticado do que os wrappers simples retornados por Collections.synchronized *
Adam Jaskiewicz
Sim, havia muitas coisas de simultaneidade legais adicionadas no J2SE 5.
Adam Jaskiewicz