ReentrantLock

Essa eh a alterativa a usar synchronized do Tiger,alguem jah testou/usou para dizer a performance desse ReeentrantLock?
Leiam aqui:
http://www-106.ibm.com/developerworks/library/j-jtp10264/

Não cheguei a precisar, e também não iria fazer um teste contra o sync. mas parece ser muito interessante.

Faltou no artigo explicar melhor isso:

:arrow: timed lock waits

Não entendi, isso não é o wait(long timeout) ???

:arrow: interruptible lock waits

Não entendi, o wait não é interrompível ??? (thows InterruptedException)

:arrow: non-block-structured locks

Não entedi essa parte !!! Alguém entendeu ???

:arrow: lock polling

Isso deve ser para aumentar a performance, não li essa parte do artigo, logo tb não entendi muito bem. :?

:arrow: multiple condition variables

Isso sim me pareceu bem legal, já que simplifica as coisas e traz mais clareza ao código.

Para entender isso eu tentei reescrever o código abaixo da maneira antiga, ou seja, com synchronized e wait/notify.

 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();
     }
   } 
 }

(Não testei nem compilei isso…)


 class BoundedBuffer {
   final Object putLock = new Object();
   final Object takeLock = new Object();
   
   final Object[] items = new Object[100];
   int putptr, takeptr, count;

   public void put(Object x) throws InterruptedException {
       synchronized(putLock) {
           // deadlock !!!!
           synchronized(takeLock) {
                while (count == items.length) takeLock.wait();
                items[putptr] = x; 
                if (++putptr == items.length) putptr = 0;
                ++count;
                putLock.notify();
           }
       }
   }

   public Object take() throws InterruptedException {
       synchronized(takeLock) {
           // deadlock !!!
           synchronized(putLock) {
                while (count == 0) putLock.wait();
                Object x = items[takeptr]; 
                if (++takeptr == items.length) takeptr = 0;
                --count;
                takeLock.notify();
                return x;
           }
       }
   } 
 }

Como falei no meu tutorial, toda vez que vc tem um lock dentro do outro uma luz vermelha deve acender indicando: DEADLOCK !!!

Como resolver esse deadlock ??? Não sei !!! Alguém sabe ??? O único jeito é voltar para um lock só:

(Não testei nem compilei isso…)


public class BoundedBuffer {

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

   public synchronized void put(Object x) throws InterruptedException {
       while (count == items.length) wait();
       items[putptr] = x; 
       if (++putptr == items.length) putptr = 0;
       ++count;
       notifyAll();
   }

   public synchronized Object take() throws InterruptedException {
       while (count == 0) wait();
       Object x = items[takeptr]; 
       if (++takeptr == items.length) takeptr = 0;
       --count;
       notifyAll();
       return x;
   }
   
}
   

Moral da história, com o ReentrantLock podemos separar em dois grupos a galera que está esperando um take e a galera que está esperando um put, e ir acordando um a um bonitinho.

Do método antigo, a galera ter que ficar num grupo só.

Só que agora deu um deadlock na minha cabeça. :crazyeyes:

Não é impossível ter dois grupos esperando ao mesmo tempo ???

Se eu tenho uma fila no take obrigatoriamente a fila do put está vazia e se eu tenho uma fila no put a fila do take obrigatoriamente está vazia.

Não faz sentido ter gente esperando para put e take ao mesmo tempo !!!

Qual a vantagem disso então ??? Acho que eu perdi algum ponto importante da história… :cry:

Da uma lida na api da classe lock, gostei da explicação de la.

http://java.sun.com/j2se/1.5.0/docs/api/java/util/concurrent/locks/Lock.html

[quote=“saoj”]
Como falei no meu tutorial, toda vez que vc tem um lock dentro do outro uma luz vermelha deve acender indicando: DEADLOCK !!!

Como resolver esse deadlock ??? Não sei !!! Alguém sabe ??? O único jeito é voltar para um lock só:[/quote]

Se voce pegar os locks sempre na mesma ordem nunca da deadlock.

Sergio, você está confundindo as coisas.
Em java, todos objetos são monitors, ou seja, lock + condition variable. Não se deve misturar essas duas primitivas na hora de escrever código MT.

Logo as considerações do autor são válidas. Com ReentrantLock a coisa é assim:

-aquisição com timeout, synchronized não permite isso.
-possibilidade de se interromper uma thread esperando pelo lock, synchronized não permite isso.
-usando synchronized seu código fica restrito ao escopo que ele foi usado. Com RL não tem dessa:

public void a() {
 ReentrantLock rl = new ReentrantLock();
 rl.lock();
 try {
  b(rl);
  c(rl);
 } finally {
   if(nãoFezNada)
     rl.unlock();
 }
}

public void b(ReentrantLock l) {
   if(condicaoB) l.unlock();
   else fazMonteDeCoisas();
}

public void c(ReentrantLock l) {
   if(condicaoC) l.unlock();
   else fazOutroMonteDeCoisas();
}

Isso permite criar código com muito menos contenção.
Outra coisa é que permite criar código mais seguro contra deadlocks usando protocolos de sincronização. Algo assim:

public interface ConcurrentCommand {
  List<? extends Lock> getRequiredLocks();
  void execute();
}

public void executeBatch(List<? extends ConcurrentCommand> cmds) {
  List<Lock> locks = new ArrayList<Lock>();
  for(i : cmds) locks.addAll(i.getRequiredLocks());
  Collections.sort(locks, new LocksComparator());
  for(c : locks) c.lock(); //não é tão simples assim, precisa tratar interrupção
  try {
   for(cc : cmds) cc.execute();
  } finally {
   for(c: locks) c.unlock();
  }
}

Isso é impossivel de ser feito usando simplesmente sinchronized. A única forma é implementando algo semelhante a ReentrantLock.

-Pooling de locks é útil, nada de especial nisso.

-Uso de múltiplas variaveis de condição. Com o mecanismo de wait/notify, você precisa de um monitor, logo 1 lock, por condição. Isso pode acabar sendo um saco se precisar usar várias condições. Com Reentrant lock as coisas ficam bem mais simples.

A moral da historia, se existe uma, é não use java.util.concurrent se não estiver escrevendo código MT muito avançado e você não for um programador muito experiênte com isso.

[quote=“louds”]Sergio, você está confundindo as coisas.
Em java, todos objetos são monitors, ou seja, lock + condition variable. Não se deve misturar essas duas primitivas na hora de escrever código MT.
[/quote]

Isso eu entendo perfeitamente !!! :?

Não sei se vc percebeu que com o ReentrantLock um lock pode ter N condiçoes agora e não apenas uma como antigamente.

Vc viu o exemplo do BoundedBuffer, que usa um lock + duas condiçoes ???

Minha duvida foi, porque no BoundedBuffer é legal ter duas condiçoes se na minha cabeca (posso estar enganado) sempre que houver gente esperando na fila do take a fila do put estará vazia e vice-versa ???

Usando uma condição apenas, isto é, usando a forma antiga de um lock + uma condição (Object) não dá no mesmo no final das contas ??? Alguma coisa eu não estou captando nessa jogada. Que todo objeto em Java é lock e monitor ao mesmo tempo isso eu já captei a algum tempo. :wink: