Dominando as Classes Auxiliares do Java Util Concurrent (JUC)

O pacote java.util.concurrent (JUC) fornece utilitários poderosos para coordenar o fluxo de execução entre múltiplas threads. Entre as ferramentas mais utilizadas para controle de sincronização avançada, destacam-se o CountDownLatch, o CyclicBarrier e o Semaphore. Essas classes permitem gerenciar estados de espera e acesso a recursos de forma mais eficiente do que o uso manual de wait() e notify().

  1. Sincronização com CountDownLatch

O CountDownLatch funciona como um contador regressivo. Ele permite que uma ou mais threads aguardem até que um conjunto de operações exceutadas em outras threads seja concluído. O contador é inicializado com um valor inteiro e diminui a cada chamada do método countDown(). As threads que chamam await() ficam bloqueadas até que o contador chegue a zero.


import java.util.concurrent.CountDownLatch;

public class ExemploCountDownLatch {
   public static void main(String[] args) throws InterruptedException {
       int totalTarefas = 4;
       CountDownLatch sincronizador = new CountDownLatch(totalTarefas);

       for (int i = 1; i <= totalTarefas; i++) {
           final int idTarefa = i;
           new Thread(() -> {
               try {
                   System.out.println("Processando sub-tarefa: " + idTarefa);
                   Thread.sleep(1000);
               } catch (InterruptedException e) {
                   e.printStackTrace();
               } finally {
                   sincronizador.countDown();
               }
           }).start();
       }

       sincronizador.await();
       System.out.println("Todas as tarefas concluídas. Iniciando processo principal.");
   }
}
   
  1. Barreiras de Execução com CyclicBarrier

Diferente do CountDownLatch, o CyclicBarrier é focado em fazer com que um grupo de threads aguarde umas pelas outras em um ponto comum de exeecução (barreira). Ele é "cíclico" porque pode ser reutilizado após a liberação das threads. É ideal para algoritmos paralelos que dividem um problema em partes e precisam sincronizar os resultados parciais antes de prosseguir.


import java.util.concurrent.CyclicBarrier;

public class ExemploCyclicBarrier {
   public static void main(String[] args) {
       int limiteParticipantes = 3;
       CyclicBarrier barreira = new CyclicBarrier(limiteParticipantes, () -> {
           System.out.println("Ponto de sincronização atingido! Executando ação de consolidação.");
       });

       for (int i = 1; i <= limiteParticipantes; i++) {
           final int workerId = i;
           new Thread(() -> {
               try {
                   System.out.println("Worker " + workerId + " chegou na barreira.");
                   barreira.await();
                   System.out.println("Worker " + workerId + " continuou a execução.");
               } catch (Exception e) {
                   e.printStackTrace();
               }
           }).start();
       }
   }
}
   
  1. Controle de Acesso com Semaphore

O Semaphore gerencia um conjunto de permissões (permits). Ele é frequentemente utilizado para limitar o número de threads que podem acessar um determinado recurso físico ou lógico simultaneamente, como um pool de conexões de banco de dados ou acesso a uma API externa.


import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;

public class ExemploSemaphore {
   public static void main(String[] args) {
       // Limitando o acesso simultâneo a apenas 2 threads
       Semaphore controleAcesso = new Semaphore(2);

       for (int i = 1; i <= 5; i++) {
           final int userId = i;
           new Thread(() -> {
               try {
                   System.out.println("Usuário " + userId + " tentando obter acesso...");
                   controleAcesso.acquire();
                   System.out.println("Usuário " + userId + " [DENTRO] do recurso crítico.");
                   
                   // Simulando tempo de uso do recurso
                   TimeUnit.SECONDS.sleep(2);
                   
               } catch (InterruptedException e) {
                   e.printStackTrace();
               } finally {
                   System.out.println("Usuário " + userId + " [SAIU] - Liberando permissão.");
                   controleAcesso.release();
               }
           }).start();
       }
   }
}
   

Resumo das Diferenças

  • CountDownLatch: Ideal para esperar que N eventos ocorram. O contador não pode ser resetado.
  • CyclicBarrier: Ideal para sincronizar N threads em um ponto de encontro. Pode ser reutilizado.
  • Semaphore: Utilizado para controle de concorrência e limitação de recursos compartilhados.

Tags: java Concurrency juc Multithreading Synchronization

Publicado em 7-10 16:58