Atualizado: 07/09/2025

Este conteúdo é original e não foi gerado por inteligência artificial.

Sincronização Explícita com ReentrantLock em Java

Além do bloqueio implícito fornecido pelo synchronized, a biblioteca padrão de Java oferece um mecanismo de bloqueio explícito: a interface Lock e sua principal implementação, a classe ReentrantLock. Ambas fazem parte do pacote java.util.concurrent.locks.

A lógica de um Lock é direta: antes de acessar um recurso compartilhado, uma thread deve primeiro adquirir o bloqueio (lock()). Se o bloqueio estiver livre, a thread o obtém e entra na seção crítica. Se outra thread já possuir o bloqueio, a thread atual fica em estado de espera. Após concluir o trabalho, é crucial que a thread libere o bloqueio (unlock()) para que outras possam prosseguir.

A interface Lock define métodos essenciais:

  • void lock(): Aguarda, se necessário, até que o bloqueio seja adquirido.
  • boolean tryLock(): Tenta adquirir o bloqueio imediatamente, sem esperar. Retorna true se bem-sucedido e false caso contrário.
  • void lockInterruptibly(): Adquire o bloqueio, mas permite que a thread em espera seja interrompida, oferecendo uma saída para longas esperas.
  • void unlock(): Libera o bloqueio.
  • Condition newCondition(): Retorna um objeto Condition associado a este Lock, que oferece uma alternativa mais flexível aos métodos wait(), notify() e notifyAll().

A Classe ReentrantLock

O nome (Reentrant) refere-se a uma propriedade importante: a mesma thread que já possui o bloqueio pode adquiri-lo novamente, de forma recursiva, sem causar um deadlock consigo mesma. O bloqueio mantém um contador interno; para cada chamada a lock(), uma chamada correspondente a unlock() deve ser feita para liberar completamente o bloqueio.

Exemplo: Substituindo synchronized por ReentrantLock

Vamos adaptar o exemplo de condição de corrida anterior para usar ReentrantLock.

import java.util.concurrent.locks.ReentrantLock;

class CommonResource {
    int x = 0;
}

class CountThread implements Runnable {
    private CommonResource res;
    private ReentrantLock locker;

    CountThread(CommonResource res, ReentrantLock lock) {
        this.res = res;
        this.locker = lock;
    }

    public void run() {
        locker.lock(); // 1. Adquire o bloqueio (bloqueia se necessário)
        try {
            // --- Início da Seção Crítica ---
            res.x = 1;
            for (int i = 1; i < 5; i++) {
                System.out.printf("%s %d \n", Thread.currentThread().getName(), res.x);
                res.x++;
                Thread.sleep(100);
            }
            // --- Fim da Seção Crítica ---
        } catch (InterruptedException e) {
            System.out.println(e.getMessage());
        } finally {
            locker.unlock(); // 2. Libera o bloqueio
        }
    }
}

public class Program {
    public static void main(String[] args) {
        CommonResource commonResource = new CommonResource();
        ReentrantLock locker = new ReentrantLock(); // Cria o objeto de bloqueio

        for (int i = 1; i < 6; i++) {
            Thread t = new Thread(new CountThread(commonResource, locker));
            t.setName("Thread " + i);
            t.start();
        }
    }
}

O padrão de uso é sempre o mesmo: adquirir o bloqueio, executar o código crítico dentro de um bloco try, e liberar o bloqueio no bloco finally. Esta estrutura é obrigatória para garantir que o bloqueio seja liberado mesmo que ocorra uma exceção, prevenindo deadlocks.

O resultado da execução será ordenado, garantindo a exclusão mútua, assim como com synchronized. A ordem exata em que as threads adquirem o bloqueio pode variar:

Thread 1 1
Thread 1 2
Thread 1 3
Thread 1 4
Thread 2 1
Thread 2 2
Thread 2 3
Thread 2 4
Thread 3 1
...

ReentrantLock vs. synchronized

Embora synchronized seja mais simples de usar, ReentrantLock oferece vantagens significativas:

  1. Flexibilidade: A aquisição e liberação do bloqueio podem ocorrer em métodos diferentes.
  2. Bloqueio não bloqueante: O método tryLock() permite verificar se um bloqueio está disponível sem bloquear a thread, possibilitando a execução de lógicas alternativas.
  3. Bloqueio interrompível: lockInterruptibly() permite que uma thread pare de esperar pelo bloqueio se for interrompida, evitando esperas indefinidas.
  4. Justiça (Fairness): O construtor ReentrantLock(true) cria um bloqueio "justo", que concede acesso às threads na ordem em que chegaram, prevenindo starvation.

Quando usar qual? Para a maioria dos cenários de sincronização simples, a sintaxe concisa do synchronized é suficiente e preferível. No entanto, para cenários mais complexos que exigem as funcionalidades avançadas listadas acima, ReentrantLock é a ferramenta mais adequada.

Resumo

  • A classe ReentrantLock é uma alternativa explícita e flexível ao synchronized.
  • Ela oferece funcionalidades avançadas como bloqueios não bloqueantes (tryLock()) e interrompíveis (lockInterruptibly()).
  • O termo Reentrant significa que uma thread pode adquirir o mesmo bloqueio múltiplas vezes sem se autobloquear.
  • É obrigatório liberar o bloqueio com unlock() dentro de um bloco finally para garantir a segurança e evitar deadlocks.
Política de Privacidade

Copyright © www.programicio.com Todos os direitos reservados

É proibida a reprodução do conteúdo desta página sem autorização prévia do autor.

Contato: programicio@gmail.com