A Interface Condition e Bloqueios ReentrantLock em Java
A interface Condition
, parte do pacote java.util.concurrent.locks
, eleva o nível da coordenação entre threads, oferecendo um mecanismo mais flexível do que os métodos wait()
e notify()
intrínsecos a cada objeto. Uma Condition
está sempre associada a uma instância de Lock
, geralmente ReentrantLock
.
Enquanto wait()
e notify()
operam em um único conjunto de espera por objeto, a principal vantagem da Condition
é que um único Lock
pode ter múltiplos objetos Condition
. Isso permite criar diferentes grupos de espera para threads, separando-as por diferentes "razões" para esperar, tudo sob a proteção do mesmo bloqueio.
Por exemplo, em um buffer limitado, é possível ter:
- Uma condição
notFull
para os produtores esperarem quando o buffer estiver cheio. - Uma condição
notEmpty
para os consumidores esperarem quando o buffer estiver vazio.
Isso permite sinalizações mais eficientes: quando um produtor adiciona um item, ele pode sinalizar (signal()
) especificamente a condição notEmpty
, acordando apenas consumidores, em vez de acordar outros produtores desnecessariamente com notifyAll()
.
Os métodos da Condition
são análogos diretos aos de Object
:
await()
: Faz com que a thread atual libere oLock
associado e entre em estado de espera nesta condição específica.signal()
: Acorda uma thread que está esperando nesta condição.signalAll()
: Acorda todas as threads que estão esperando nesta condição.
Esses métodos devem ser invocados apenas quando a thread atual detém o Lock
associado à Condition
.
Exemplo: Produtor-Consumidor com ReentrantLock e Condition
Vamos refatorar o problema do Produtor-Consumidor para usar ReentrantLock
e uma única Condition
. Este exemplo demonstra a substituição direta de synchronized
e wait/notify
.
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
class Store {
private int productCount = 0;
private final int MAX_PRODUCTS = 3;
private ReentrantLock locker;
private Condition condition;
Store() {
locker = new ReentrantLock();
condition = locker.newCondition(); // Obtém UMA condição associada ao bloqueio
}
public void get() {
locker.lock();
try {
while (productCount < 1) {
System.out.println("Estoque vazio, consumidor esperando...");
condition.await();
}
productCount--;
System.out.println("Consumidor comprou 1 produto.");
System.out.println("Produtos no estoque: " + productCount);
condition.signalAll(); // Sinaliza para as threads em espera (produtor)
} catch (InterruptedException e) {
System.err.println(e.getMessage());
Thread.currentThread().interrupt();
} finally {
locker.unlock();
}
}
public void put() {
locker.lock();
try {
while (productCount >= MAX_PRODUCTS) {
System.out.println("Estoque cheio, produtor esperando...");
condition.await();
}
productCount++;
System.out.println("Produtor adicionou 1 produto.");
System.out.println("Produtos no estoque: " + productCount);
condition.signalAll(); // Sinaliza para as threads em espera (consumidor)
} catch (InterruptedException e) {
System.err.println(e.getMessage());
Thread.currentThread().interrupt();
} finally {
locker.unlock();
}
}
}
// Classes Producer e Consumer, e a classe Program com o método main, permanecem as mesmas.
// ...
O fluxo é idêntico ao da versão com wait/notify
, mas o controle é explícito através dos objetos locker
e condition
. O método await()
libera o bloqueio, permitindo que a outra thread o adquira. Após ser sinalizada, a thread que estava em espera deve readquirir o bloqueio antes de continuar.
O resultado da execução demonstra a coordenação:
Produtor adicionou 1 produto. Produtos no estoque: 1 Produtor adicionou 1 produto. Produtos no estoque: 2 Produtor adicionou 1 produto. Produtos no estoque: 3 Estoque cheio, produtor esperando... Consumidor comprou 1 produto. Produtos no estoque: 2 ...
Resumo
- A interface
Condition
é uma alternativa flexível aos métodoswait()
enotify()
. - Um objeto
Condition
é sempre obtido a partir de umLock
(locker.newCondition()
). - Os métodos
await()
,signal()
esignalAll()
só podem ser chamados quando a thread detém oLock
associado. - A principal vantagem é a capacidade de um
Lock
ter múltiplos objetosCondition
, permitindo a criação de diferentes conjuntos de espera e sinalizações mais eficientes. - O padrão de uso deve sempre seguir a estrutura
lock()
,try
,await/signal
,finally
,unlock()
.