Conditions no java.util.concurrent do Tiger

Acho que me perdi no raciocínio abaixo, logo se alguém quiser comentar algo para me ajudar seria legal.

No exemplo da documentação da interface Condition da nova API de concorrência do Tiger, o seguinte exemplo é utilizado para demonstrar uma possível vantagem dessa abordagem:

 class BoundedBuffer {
   final Lock lock = new ReentrantLock();
   final Condition notFull  = lock.newCondition(); 
   final Condition notEmpty = lock.newCondition(); 

   final Object[] items = new Object[100];
   int putptr, takeptr, count;

   public void put(Object x) throws InterruptedException {
     lock.lock();
     try {
       while (count == items.length) 
         notFull.await();
       items[putptr] = x; 
       if (++putptr == items.length) putptr = 0;
       ++count;
       notEmpty.signal();
     } finally {
       lock.unlock();
     }
   }

   public Object take() throws InterruptedException {
     lock.lock();
     try {
       while (count == 0) 
         notEmpty.await();
       Object x = items[takeptr]; 
       if (++takeptr == items.length) takeptr = 0;
       --count;
       notFull.signal();
       return x;
     } finally {
       lock.unlock();
     }
   } 
 }

Mas não seria impossível ter threads esperando em ambas as condições simultaneamente ??? Se o queue está cheio, ele não está vazio e se ele está vazio logo não está cheio. (duh!) Logo se tenho threads esperando numa condition (notFull) não terei threads esperando na outra condition (notEmpty) e vice-versa. Então pra que ter duas condições e não apenas uma ???

 class BoundedBuffer {
   final Lock lock = new ReentrantLock();
   final Condition hadToWait  = lock.newCondition(); 

   final Object[] items = new Object[100];
   int putptr, takeptr, count;

   public void put(Object x) throws InterruptedException {
     lock.lock();
     try {
       while (count == items.length) 
         hadToWait.await();
       items[putptr] = x; 
       if (++putptr == items.length) putptr = 0;
       ++count;
       hadToWait.signal();
     } finally {
       lock.unlock();
     }
   }

   public Object take() throws InterruptedException {
     lock.lock();
     try {
       while (count == 0) 
         hadToWait.await();
       Object x = items[takeptr]; 
       if (++takeptr == items.length) takeptr = 0;
       --count;
       hadToWait.signal();
       return x;
     } finally {
       lock.unlock();
     }
   } 
 }

Perguntas:

  1. Estou certo em achar que o exemplo utilizado na documentação não é um bom exemplo onde mais de uma condição para um lock é necessário ?

  2. Caso a resposta acima seja positiva, qual seria um exemplo realmente gritante onde duas condições para um único lock é essencial ?

O exemplo da API é brilhante em mostrar como múltiplas condições é util.

A situação que você descreve é impossivel, mostre uma seqüência de puts takes que causaria isso.

O uso de 2 condições melhora a performance dado não precisamos usar signalAll() e vão esperar na fila e receber o sinal somente quem precisa.

A sua versão pode causar deadlocks, por sinal.

Seria legal mostrar argumentos dentro do contexto da minha dúvida. SignalAll está fora do contexto da minha dúvida. Vc enfatizou bastante que eu estou errado, mas pouco ajudou na questão que eu levantei. Pelo menos não sugeriu um boicote ao post como fez aqui: http://www.guj.com.br/posts/list/18374.java

[color=red]Por favor: Não fale que eu estou errado porque eu estou errado e vc está certo. Não fale que o meu código tem deadlock sem mostrar aonde. Não porque eu não admito que os meus códigos tem deadlock, claro que as vezes tem, apenas quero aprender com os meus erros e se vc fala que está errado sem mostrar aonde não ajuda e soa arrogante. Não fale que signalAll explica tudo quando ele nem está em discussão aqui. Estou pedindo ajuda e não uma resposta desse tipo. Isso é desistimulante demais. Sempre quando eu posto aqui eu penso um milhão de vezes se devo. E na maioria das vezes me arrependo. Se eu fui humilde o suficiente para dizer que não sei e quero ajuda, então ajude de verdade e não apenas esculaxe sem argumentos claros. Uma resposta arrogante e pouco clara como essa DESESTIMULA as outras pessoas a entrarem no debate. Pense nisso. Seja humilde. Pelo amor de Deus e pelo bem do fórum.[/color]

Pergunta 1)

Repito minha informação e espero um argumento que a refute: na minha cabeça, e posso estar errado, é impossível ter threads esperando ao mesmo tempo em ambas as condições. Quando tivermos threads em notEmpty não teremos threads em notFull e vice-versa. Essas condições são mutuamente exclusivas. Se vc acha essa afirmação impossível vc é que tem que mostrar a sequencia de puts e takes que a contradiz, não eu.

Eu é que estou perguntando se existe alguma sequência de puts e takes que vai fazer com ambas as condições possuam threads diferentes travados simultaneamente. Na minha cabeça não existe tal combinação, logo pra que ter duas condições ao invés de uma ???

Pergunta 2)

Não precisamos usar signalAll em nenhuma condição. signalAll não está na discussão aqui. Estamos usando signal apenas e é suficiente. Dá uma lida no meu código e veja se tem algum signalAll lá.

Pergunta 3)

Me mostre o deadlock no meu código. Um lock e uma condição apenas gerando deadlock é algo que eu realmente quero aprender.

Louds, a dúvida do saoj é pertinente.
As condições não são de mútua exclusão???Desculpe minha ignorância, mas existe um pool de threads q esperam da mesma forma??? :?:

Você não pediu uma explicação, só se estava ou não correto. :lol:
O signalAll() cai como uma luva aqui, porque é um artificio que precisa ser usado quando temos mais de uma condição e somente um monitor.

  1. Vamos lá, 1 pouco de lógica

SE (count == items.length) notFull.await();
SE (count == 0) notEmpty.await();

Vale lembrar que como o mesmo objeto Lock é usado, o código de ambos os métodos executa de forma mutualmente exclusiva.

Para termos esperas em ambas as filas, (count == items.length) e (count == 0) tem que ser verdadeiros ao mesmo tempo. Com mais um pouco de lógica temos que items.length == 0 é verdadeiro.

A prova não é tão simples quanto mostrei, mas da para ver que somente com um array de tamanho zero que vai ocorrer um deadlock. Já que o código usa tamanho 100 e zero não é um valor razoavel, da para atestar a corretude dele.

  1. Você ficou tão de cabeça quente que não leu direito oque eu falei. Quando temos duas condições de espera mas implementamos usando somente um monitor (monitor = 1 lock + 1 codição) precisamos usar o signalAll(). Seu código está errado exatamente por não usar signalAll(), com ele funcionaria corretamente.

3)Seu código possui 2 situações diferentes de espera, isso normalmente precisa de múltiplas “Conditions” ou signalAll.

Vamos lá

Buffer de tamanho 1

3 threads fazem put
1 ok, 2 put esperando na condição
2 threads fazem take
put sinaliza a condição, ficamos com 1 put e 1 take esperando o lock.
take ganha e acaba a esperar na condição
2 ok, 1 put e 1 take a esperar na condição e 1 put pronto
put sinaliza a condição
put ganha e acaba na fila
resultado final, 1 put e 1 take esperando na fila

Com signalAll, esse erro é corrigido.

Agora repete esse procedimento quando tem as 2 condições…

[color=red]Apenas uma condição (hasToWait)[/color]

Buffer de tamanho 1

3 threads fazem put

1 ok (executa, coloca o valor e retorna) e 2 put ficam esperando na condição hasToWait

2 threads fazem take, um vai pegar lock e executar com sucesso e sinalizar a condição hasToWait

agora temos um dos puts que foi acordao pela sinalização anterior brigando com o segundo take que também quer executar.

take ganha o lock, mas encontra o buffer vazio e acaba esperando na condição hasToWait, que como vc falou já tem um put daquele grupo inicial de 3 esperando… (hummm)

o put que perdeu a briga com o take anterior, agora pode executar, isto é, pode pegar o lock que o take anterior liberou quando esperou na condição

2 ok (executa, coloca o valor e retorna) e sinaliza a condição, que se acordar o take anterior que encontrou o buffer vazio não causará problemas, [color=green]mas se acordar o put que está lá desde o início (grupo inicial de três) teremos um put e um take parados na condição o que é bastante errado.[/color]

Conclusão: Uma condição apenas como eu sugeri é errado, não pode ser utilizado com signal, apenas com signalAll como o louds falou, o que é ineficiente.

[color=red]Agora vamos avaliar com duas condições (notFull e notEmpty)[/color]

Buffer de tamanho 1

3 threads fazem put

1 ok (executa, coloca o valor e retorna) e 2 put ficam esperando na condição notFull (um put só pode executar quando o buffer está notFull)

2 threads fazem take, um vai pegar lock e executar com sucesso e sinalizar a condição notFull

agora temos um dos puts que foi acordao pela sinalização anterior brigando com o segundo take que também quer executar.

take ganha o lock, mas encontra o buffer vazio e acaba esperando na condição notEmpty (um take só pode executar quando o buffer está notEmpty) Nesse ponto temos dois threads diferentes cada qual esperando em notFull e notEmpty, logo a minha afirmação inicial está errada: notFull e notEmpty não são mutuamente exclusivas e podem ter threads esperando simultaneamente como nesse caso.

o put que perdeu a briga com o take anterior, agora pode executar, isto é, pode pegar o lock que o take anterior liberou quando esperou na condição notFull

2 ok (executa, coloca o valor e retorna) e sinaliza a condição notEmpty, que acorda um take corretamente e sem a necessidade de signalAll.

Obrigado louds, acho que agora eu e as outras pessoas conseguiram entender o X da questão: diferentemente do que eu falei, é possível sim ter vários threads em notEmpty e vários threads em notFull, e o fato de termos duas condições separadas nos permite acordar os threads certos um de cada vez, sem o overhead de signalAll. Com os monitores tradicionais do Java (um lock = uma condição apenas), tínhamos que utilizar notifyAll nesses casos, o que é ineficiente.