DEV Community

Java Efetivo (livro)
Java Efetivo (livro)

Posted on

Item 81: Prefira os utilitários de concorrência ao wait e notify

Motivação

  • Desde o Java 5, a plataforma oferece utilitários de concorrência de alto nível no pacote java.util.concurrent.
  • Eles substituem o uso manual e complexo de wait e notify.
  • São mais seguros e fáceis de usar, reduzindo a probabilidade de erros em código concorrente.

Utilitários de Concorrência no java.util.concurrent
Categorias de utilitários:

  • Executor Framework: Gerenciamento de threads abordado no Item 80.
  • Coleções concorrentes: Implementações thread-safe de coleções padrão, como List, Queue e Map.
  • Sincronizadores: Coordenação entre threads, incluindo CountDownLatch, Semaphore, CyclicBarrier, Exchanger e Phaser.

Coleções Concorrentes

Características:

  • Internamente sincronizadas para alto desempenho.
  • Não permitem exclusão de atividade concorrente.
  • Operações atômicas como putIfAbsent aumentam a segurança e a usabilidade. Exemplo: Implementação de um Map thread-safe:
Map<String, String> map = new ConcurrentHashMap<>();
String result = map.putIfAbsent("key", "value");
if (result == null) {
    System.out.println("Valor inserido.");
} else {
    System.out.println("Chave já existente com valor: " + result);
}

Enter fullscreen mode Exit fullscreen mode

Benefícios:

  • Substituem coleções sincronizadas (Collections.synchronizedMap).
  • Melhora significativa no desempenho de aplicações concorrentes.

Sincronizadores
Finalidade: Coordenação entre threads.

Exemplo de sincronizadores comuns:

  • CountDownLatch: Barreira de uso único para coordenação de threads.
  • Semaphore: Controle de acesso a recursos compartilhados.
  • CyclicBarrier: Sincronização em pontos de barreira reutilizáveis.
  • Phaser: Sincronização avançada e dinâmica de threads.

Exemplo Prático: Cronometragem Concorrente com CountDownLatch
Objetivo: Medir o tempo de execução de várias threads concorrentemente.

Implementação:

public static long time(Executor executor, int concurrency, Runnable action) throws InterruptedException {
    CountDownLatch ready = new CountDownLatch(concurrency);
    CountDownLatch start = new CountDownLatch(1);
    CountDownLatch done = new CountDownLatch(concurrency);

    for (int i = 0; i < concurrency; i++) {
        executor.execute(() -> {
            try {
                ready.countDown(); // Indica que está pronto
                start.await();     // Aguarda o sinal de início
                action.run();
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            } finally {
                done.countDown(); // Indica que terminou
            }
        });
    }

    ready.await();   // Aguarda todas as threads ficarem prontas
    long startTime = System.nanoTime();
    start.countDown(); // Dispara o sinal de início
    done.await();     // Aguarda todas as threads finalizarem
    return System.nanoTime() - startTime;
}

Enter fullscreen mode Exit fullscreen mode

Notas:

  • Usa três travas: ready (indica prontidão), start (disparo de início) e done (finalização).
  • Usa System.nanoTime para medir intervalos de tempo com precisão.

Prática Corrente com wait e notify
Apenas necessário para manutenção de código legado.
Regras principais:

  1. Sempre utilize um loop ao chamar wait:
synchronized (lock) {
    while (!condition) {
        lock.wait();
    }
}

Enter fullscreen mode Exit fullscreen mode
  1. Teste a condição antes e depois da espera.
  2. Evite dependência de notify, prefira notifyAll.

Conclusão

  • Utilize os utilitários de concorrência sempre que possível.
  • Eles tornam o código mais legível, seguro e eficiente.
  • Alternativas modernas (como CyclicBarrier ou Phaser) podem substituir padrões baseados em wait e notify

Exemplos do livro

Image description

Image description

Image description

Image description

Top comments (0)