ExecutorService - Aguardando o término dos threads
1. Visão geral
A estruturaExecutorService facilita o processamento de tarefas em vários threads. Vamos exemplificar alguns cenários em que esperamos que os threads concluam sua execução.
Além disso, mostraremos como desligar normalmente umExecutorServicee aguardar que threads já em execução terminem sua execução.
2. Após desligamento deExecutor’s
Ao usar umExecutor,, podemos desligá-lo chamando os métodosshutdown() oushutdownNow(). Although, it won’t wait until all threads stop executing.
Esperar que os threads existentes concluam sua execução pode ser obtido usando o métodoawaitTermination().
Isso bloqueia o encadeamento até que todas as tarefas concluam sua execução ou o tempo limite especificado seja atingido:
public void awaitTerminationAfterShutdown(ExecutorService threadPool) {
threadPool.shutdown();
try {
if (!threadPool.awaitTermination(60, TimeUnit.SECONDS)) {
threadPool.shutdownNow();
}
} catch (InterruptedException ex) {
threadPool.shutdownNow();
Thread.currentThread().interrupt();
}
}
3. UsandoCountDownLatch
A seguir, vamos dar uma olhada em outra abordagem para resolver este problema - usando umCountDownLatch para sinalizar a conclusão de uma tarefa.
Podemos inicializá-lo com um valor que representa o número de vezes que ele pode ser decrementado antes que todas as threads, que chamaram o métodoawait(), sejam notificadas.
Por exemplo, se precisarmos que o thread atual espere por outros threadsN concluírem sua execução, podemos inicializar a trava usandoN:
ExecutorService WORKER_THREAD_POOL
= Executors.newFixedThreadPool(10);
CountDownLatch latch = new CountDownLatch(2);
for (int i = 0; i < 2; i++) {
WORKER_THREAD_POOL.submit(() -> {
try {
// ...
latch.countDown();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
});
}
// wait for the latch to be decremented by the two remaining threads
latch.await();
4. UsandoinvokeAll()
A primeira abordagem que podemos usar para executar threads é o métodoinvokeAll(). The method returns a list of Future objects after all tasks finish or the timeout expires.
Além disso, devemos observar que a ordem dos objetosFuture retornados é a mesma que a lista dos objetosCallable fornecidos:
ExecutorService WORKER_THREAD_POOL = Executors.newFixedThreadPool(10);
List> callables = Arrays.asList(
new DelayedCallable("fast thread", 100),
new DelayedCallable("slow thread", 3000));
long startProcessingTime = System.currentTimeMillis();
List> futures = WORKER_THREAD_POOL.invokeAll(callables);
awaitTerminationAfterShutdown(WORKER_THREAD_POOL);
long totalProcessingTime = System.currentTimeMillis() - startProcessingTime;
assertTrue(totalProcessingTime >= 3000);
String firstThreadResponse = futures.get(0).get();
assertTrue("fast thread".equals(firstThreadResponse));
String secondThreadResponse = futures.get(1).get();
assertTrue("slow thread".equals(secondThreadResponse));
5. UsandoExecutorCompletionService
Outra abordagem para executar vários threads é usarExecutorCompletionService.. Ele usaExecutorService fornecido para executar tarefas.
Uma diferença em relação ainvokeAll() é a ordem em que osFutures, que representam as tarefas executadas são retornados. ExecutorCompletionService uses a queue to store the results in the order they are finished, enquantoinvokeAll() retorna uma lista com a mesma ordem sequencial produzida pelo iterador para a lista de tarefas fornecida:
CompletionService service
= new ExecutorCompletionService<>(WORKER_THREAD_POOL);
List> callables = Arrays.asList(
new DelayedCallable("fast thread", 100),
new DelayedCallable("slow thread", 3000));
for (Callable callable : callables) {
service.submit(callable);
}
Os resultados podem ser acessados usando o métodotake():
long startProcessingTime = System.currentTimeMillis();
Future future = service.take();
String firstThreadResponse = future.get();
long totalProcessingTime
= System.currentTimeMillis() - startProcessingTime;
assertTrue("First response should be from the fast thread",
"fast thread".equals(firstThreadResponse));
assertTrue(totalProcessingTime >= 100
&& totalProcessingTime < 1000);
LOG.debug("Thread finished after: " + totalProcessingTime
+ " milliseconds");
future = service.take();
String secondThreadResponse = future.get();
totalProcessingTime
= System.currentTimeMillis() - startProcessingTime;
assertTrue(
"Last response should be from the slow thread",
"slow thread".equals(secondThreadResponse));
assertTrue(
totalProcessingTime >= 3000
&& totalProcessingTime < 4000);
LOG.debug("Thread finished after: " + totalProcessingTime
+ " milliseconds");
awaitTerminationAfterShutdown(WORKER_THREAD_POOL);
6. Conclusão
Dependendo do caso de uso, temos várias opções para aguardar que os threads concluam sua execução.
UmCountDownLatch é útil quando precisamos de um mecanismo para notificar uma ou mais threads que um conjunto de operações realizadas por outras threads terminou.
ExecutorCompletionService é útil quando precisamos acessar o resultado da tarefa o mais rápido possível e outras abordagens quando queremos esperar que todas as tarefas em execução sejam concluídas.
O código-fonte do artigo está disponívelover on GitHub.