Domínio das Ferramentas de Sincronização do java.util.concurrent em Java

ReentrantLock

O ReentrantLock fornece uma implementação de exclusão mútua reentrante que supera as limitações do bloco synchronized tradicional. Entre suas capacidades avançadas, destaca-se a possibilidade de interrupção durante a tentativa de aquisição, suporte a políticas de justiça (fairness) para evitar inanição de threads, bloqueios com limites de tempo configuráveis e a associação de múltiplas variáveis de condição (Condition). É a escolha ideal para cenários de alta concorrência que exigem controle refinado sobre o estado compartilhado.

import java.util.concurrent.locks.ReentrantLock;

public class GerenciadorDeSaldo {
    // Política justa ativada para garantir a ordem de requisição
    private final ReentrantLock bloqueio = new ReentrantLock(true); 
    private double saldoDisponivel = 1000.0;

    public void processarSaque(double valorRequisitado) {
        bloqueio.lock();
        try {
            if (saldoDisponivel >= valorRequisitado) {
                // Simulação de latência de processamento
                saldoDisponivel -= valorRequisitado;
            }
        } finally {
            // O unlock deve ocorrer obrigatoriamente no bloco finally
            bloqueio.unlock(); 
        }
    }
}

Semaphore

A classe Semaphore atua como um controlador de fluxo de recursos, retsringindo o número máximo de threads que podem operar simultaneamente em uma seção crítica por meio de um sistema de permissões. Este mecanismo é amplamente adotado na engenharia de software para gerenciar pools de conexões com banco de dados, limitar taxas de requisições de APIs ou controlar o acesso a hardware específico.

import java.util.concurrent.Semaphore;

public class PoolDeConexoes {
    // Configurado para permitir no máximo 5 acessos simultâneos
    private final Semaphore semaforo = new Semaphore(5);

    public void executarTransacao() throws InterruptedException {
        // Bloqueia a thread caso o limite de permissões seja atingido
        semaforo.acquire(); 
        try {
            // Lógica de interação com o banco de dados
            Thread.sleep(100); 
        } finally {
            // Devolve a permissão para que outra thread possa prosseguir
            semaforo.release(); 
        }
    }
}

CountDownLatch

Projetado para coordenação unidirecional, o CountDownLatch permite que uma ou mais threads entrem em estado de espera até que um conjunto pré-definido de operações, executadas por outras threads, seja totalmente concluído. O contador interno é decrementado de forma atômica e, ao atingir zero, todas as threads bloqueadas são liberadas automaticamente. É fundamental compreender que esta classe é de uso único e não pode ser reiniciada.

import java.util.concurrent.CountDownLatch;

public class InicializadorDeSistema {
    public static void main(String[] args) throws InterruptedException {
        CountDownLatch inicializacao = new CountDownLatch(4);

        for (int i = 1; i <= 4; i++) {
            final int idServico = i;
            new Thread(() -> {
                System.out.println("Inicializando microsserviço: " + idServico);
                inicializacao.countDown(); // Sinaliza a conclusão da tarefa
            }).start();
        }

        // A thread principal aguarda até que o contador chegue a zero
        inicializacao.await(); 
        System.out.println("Todos os microsserviços estão operacionais.");
    }
}

CyclicBarrier

Diferentemente do CountDownLatch, o CyclicBarrier exige que um grupo de threads aguarde umas pelas outras em um ponto de sincronização comum antes que qualquer uma delas possa avançar. Sua natureza cíclica permite que a instância seja reutilizada após a liberação das threads, sendo a estrutura perfeita para algoritmos iterativos, simulações multipassos ou processamentos em lote divididos em etapas sequenciais.

import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.BrokenBarrierException;

public class ProcessamentoDeDados {
    public static void main(String[] args) {
        int totalDeNos = 3;
        CyclicBarrier barreira = new CyclicBarrier(totalDeNos, () -> {
            System.out.println("--- Fase de processamento concluída. Iniciando a próxima. ---");
        });

        for (int i = 0; i < totalDeNos; i++) {
            new Thread(() -> {
                try {
                    // Execução do primeiro bloco de trabalho
                    barreira.await(); 
                    // Execução do segundo bloco de trabalho
                    barreira.await();
                } catch (InterruptedException | BrokenBarrierException e) {
                    Thread.currentThread().interrupt();
                }
            }).start();
        }
    }
}

Exchanger

O Exchanger estabelece um ponto de encontro síncrono onde exatamente duas threads podem trocar instâncias de objetos de forma segura. A thread que chega primeiro ao ponto de troca fica suspensa até que a segunda thread chegue. Nesse momento, ambas trocam suas referências de dados e continuam suas execuções independentemente. Esse padrão é frequentemente aplicado em pipelines de validação, algoritmos genéticos ou sistemas de negociação de chaves criptográficas.

import java.util.concurrent.Exchanger;

public class CanalDeComunicacao {
    public static void main(String[] args) {
        Exchanger<string> canal = new Exchanger<>();

        Thread threadA = new Thread(() -> {
            try {
                String payload = "Dados Criptografados";
                String resposta = canal.exchange(payload);
                System.out.println("Thread A obteve a chave: " + resposta);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        });

        Thread threadB = new Thread(() -> {
            try {
                String chave = "Chave de Descriptografia RSA";
                String dadosRecebidos = canal.exchange(chave);
                System.out.println("Thread B processou o pacote: " + dadosRecebidos);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        });

        threadA.start();
        threadB.start();
    }
}
</string>

Phaser

Considerada a API de sincronização mais avançada e flexível do pacote JUC, o Phaser combina as funcionalidades do CountDownLatch e do CyclicBarrier. Seu grande diferencial é o suporte ao registro e cancelamento dinâmico de participantes, além da capacidade de gerenciar fluxos de trabalho complexos divididos em múltiplas fases. Threads podem se juntar ou abandonar a sincronização a qualquer momento, adaptando-se a cargas de trabalho imprevisíveis.

import java.util.concurrent.Phaser;

public class PipelineDinamico {
    public static void main(String[] args) {
        // A thread principal se registra como participante
        Phaser sincronizador = new Phaser(1); 

        for (int i = 0; i < 3; i++) {
            sincronizador.register(); // Adição dinâmica de novas threads
            new Thread(() -> {
                // Processamento da Etapa 1
                sincronizador.arriveAndAwaitAdvance(); 
                
                // Processamento da Etapa 2
                sincronizador.arriveAndDeregister(); // Thread abandona o sincronizador após concluir
            }).start();
        }

        // A thread principal aguarda o término da primeira fase de todos os workers
        sincronizador.arriveAndAwaitAdvance(); 
        System.out.println("Orquestração da primeira fase finalizada com sucesso.");
        
        // A thread principal se deregistra para encerrar o ciclo
        sincronizador.arriveAndDeregister();
    }
}

Tags: java Concorrência juc ReentrantLock semaphore

Publicado em 7-23 15:30