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();
}
}