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);
}
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;
}
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:
- Sempre utilize um loop ao chamar wait:
synchronized (lock) {
    while (!condition) {
        lock.wait();
    }
}
- Teste a condição antes e depois da espera.
- 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
 





 
    
Top comments (0)