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
notFullpara os produtores esperarem quando o buffer estiver cheio. - Uma condição
notEmptypara 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 oLockassociado 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 oLockassociado. - A principal vantagem é a capacidade de um
Lockter 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().