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)