Fundamentos de Exclusão Mútua
A classe ReentrantLock fornece um mecanismo de bloqueio mais flexível e abrangente do que o modificador intrínseco synchronized. O uso básico exige que o desenvolvedor gerencie explicitamente a aquisição e a liberação do recurso.
public class SharedResource {
private final Lock mutex = new ReentrantLock();
public void executeCriticalTask() {
mutex.lock();
try {
for (int i = 0; i < 3; i++) {
System.out.println(Thread.currentThread().getName() + " processando item " + i);
}
} finally {
// A liberação deve ocorrer obrigatoriamente no bloco finally
mutex.unlock();
}
}
}
Para testar a exclusão mútua, múltiplas threads podem compartilhar a mesma instância. A saída demonstrará que a execução é serializada, garantindo que apenas uma thread por vez acesse a seção crítica.
Independência de Monitores
Uma diferença arquitetural crucial é que o ReentrantLock não utiliza o monitor intrínseco do objeto Java. Portanto, um bloqueio explícito e um bloco synchronized no mesmo objeto não competem entre si.
public class DualMonitorDemo {
private final Lock explicitLock = new ReentrantLock();
public void taskWithExplicitLock() {
explicitLock.lock();
try {
System.out.println(Thread.currentThread().getName() + " iniciou com ReentrantLock");
Thread.sleep(3000);
System.out.println(Thread.currentThread().getName() + " finalizou com ReentrantLock");
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
explicitLock.unlock();
}
}
public synchronized void taskWithImplicitLock() {
System.out.println(Thread.currentThread().getName() + " executou com synchronized");
}
}
Se duas threads invocarem simultaneamente taskWithExplicitLock e taskWithImplicitLock, ambas prosseguirão de forma assíncrona, comprovando que operam em domínios de sincronização distintos.
Coordenação de Threads com Condition
Enquanto synchronized depende de wait() e notify(), o ReentrantLock utiliza a interface Condition. Isso permite que uma thread aguarde e libere o lock atomicamente.
public class SignalCoordinator {
private final Lock lock = new ReentrantLock();
private final Condition condition = lock.newCondition();
public void waitForSignal() {
lock.lock();
try {
System.out.println("Aguardando sinal...");
condition.await();
System.out.println("Sinal recebido, retomando execução.");
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
lock.unlock();
}
}
public void sendSignal() {
lock.lock();
try {
System.out.println("Enviando sinal...");
condition.signal();
} finally {
lock.unlock();
}
}
}
Notificação Seletiva
A principal vantagem do Condition é a capacidade de criar múltiplas filas de espera para um único lock. Isso resolve o problema do "despertar aleatório" do notify(), permitindo notificações direcionadas.
public class BoundedBuffer {
private final Lock lock = new ReentrantLock();
private final Condition notFull = lock.newCondition();
private final Condition notEmpty = lock.newCondition();
private final Queue<String> queue = new LinkedList<>();
private final int capacity = 5;
public void produce(String item) {
lock.lock();
try {
while (queue.size() == capacity) {
notFull.await(); // Produtores aguardam se a fila estiver cheia
}
queue.add(item);
System.out.println("Produzido: " + item + " | Tamanho: " + queue.size());
notEmpty.signal(); // Notifica apenas os consumidores
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
lock.unlock();
}
}
public void consume() {
lock.lock();
try {
while (queue.isEmpty()) {
notEmpty.await(); // Consumidores aguardam se a fila estiver vazia
}
String item = queue.poll();
System.out.println("Consumido: " + item + " | Tamanho: " + queue.size());
notFull.signal(); // Notifica apenas os produtores
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
lock.unlock();
}
}
}
API de Inspeção e Controle
Locks Justos e Injustos
O construtor do ReentrantLock aceita um booleano para definir a política de concessão. Locks justos (fair = true) garantem que a ordem de aquisição respeite a ordem de chegada na fila, enquanto o padrão (fair = false) permite que threads recém-chegadas "furarem a fila" para reduzir o tempo de inatividade (thread starvation).
public class FairnessDemo {
// true para FairLock, false para UnfairLock (padrão)
private final Lock fairLock = new ReentrantLock(true);
public void execute() {
fairLock.lock();
try {
System.out.println(Thread.currentThread().getName() + " adquiriu o lock justo.");
} finally {
fairLock.unlock();
}
}
}
Métodos de Diagnóstico
A API expõe métodos úteis para depuração e monitoramento do estado do lock:
getHoldCount(): Retorna quantas vezes a thread atual adquiriu o lock (suporte a reentrância).getQueueLength(): Estima o número de threads aguardando para adquirir o lock.isFair(): Verifica se o lock foi configurado como justo.hasQueuedThread(Thread t)ehasQueuedThreads(): Verificam se uma thread específica ou qualquer thread está na fila de espera.isHeldByCurrentThread(): Confirma se a thread atual é a detentora do lock.isLocked(): Indica se o lock está sendo mantido por alguma thread.
Aquisição Não-Bloqueante
Os métodos tryLock() e tryLock(long timeout, TimeUnit unit) tentam adquirir o lock sem bloquera a thread indefinidamente. Isso é fundamental para prevenir deadlocks e implementar lógicas de fallback.
public class SafeAcquisition {
private final Lock lock = new ReentrantLock();
public void performAction() {
// Tenta adquirir o lock por no máximo 2 segundos
if (lock.tryLock(2, TimeUnit.SECONDS)) {
try {
System.out.println("Lock adquirido com sucesso. Executando ação.");
// Simulação de trabalho
Thread.sleep(1000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
lock.unlock();
}
} else {
System.out.println("Falha ao adquirir o lock no tempo limite. Executando lógica alternativa.");
}
}
}
Comparativo: synchronized vs ReentrantLock
- Implementação:
synchronizedé uma palavra-chave nativa da JVM (nível de sintaxe), enquantoReentrantLocké uma classe da API Java (java.util.concurrent.locks) que exige chamadas explícitas delock()eunlock(). - Política de Concessão:
synchronizedatua estritamente como um lock injusto.ReentrantLockoferece a opção de alternar entre comportamento justo e injusto. - Flexibilidade de Notificação: O mecanismo de espera/notificação de
synchronized(wait/notify) é limitado a um único conjunto de espera por objeto.ReentrantLockpermite a criação de múltiplos objetosCondition, habilitando notificações seletivas e precisas.