1.概述
该ExecutorService框架可以很容易地在处理多线程任务。我们将举例说明我们等待线程完成执行的一些场景。
此外,我们将展示如何正常关闭ExecutorService并等待已经运行的线程完成其执行。
2.Executor关闭后
使用Executor时,我们可以通过调用shutdown()或shutdownNow()方法将其关闭。虽然,它不会等到所有线程都停止执行。
等待现有线程完成执行可以通过使用awaitTermination()方法来实现。
这会阻塞线程,直到所有任务完成执行或达到指定的超时:
public void awaitAfterShutdown(ExecutorService threadPool) {
threadPool.shutdown();
try {
if (!threadPool.awaitTermination(60, TimeUnit.SECONDS)) {
threadPool.shutdownNow();
}
} catch (InterruptedException ex) {
threadPool.shutdownNow();
Thread.currentThread().interrupt();
}
}
3.使用CountDownLatch
接下来,让我们看看解决此问题的另一种方法 - 使用CountDownLatch来表示任务的完成。
我们可以使用一个值来初始化它,该值表示在通知所有调用await()方法的线程之前可以递减的次数。
例如,如果我们需要当前线程等待另外N个线程完成执行,我们可以使用N初始化锁存器:
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.使用invokeAll()
我们可以用来运行线程的第一种方法是invokeAll()方法。在所有任务完成或超时到期后,该方法返回Future对象列表。
此外,我们必须注意返回的Future对象的顺序与提供的Callable对象的列表相同:
ExecutorService WORKER_THREAD_POOL = Executors.newFixedThreadPool(10);
List<Callable<String>> callables = Arrays.asList(new DelayedCallable("fast thread", 100),new DelayedCallable("slow thread", 3000));
long startProcessingTime = System.currentTimeMillis();
List<Future<String>> 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.使用ExecutorCompletionService
运行多个线程的另一种方法是使用ExecutorCompletionService。它使用提供的ExecutorService来执行任务。
与invokeAll()的一个区别是返回表示执行任务的Futures的顺序。ExecutorCompletionService使用队列按结束顺序存储结果,而invokeAll()返回一个列表,该列表具有与给定任务列表的迭代器生成的顺序相同的顺序:
CompletionService<String> service = new ExecutorCompletionService<>(WORKER_THREAD_POOL);
List<Callable<String>> callables = Arrays.asList(new DelayedCallable("fast thread", 100),new DelayedCallable("slow thread", 3000));
for (Callable<String> callable : callables) {
service.submit(callable);
}
可以使用take()方法访问结果:
long startProcessingTime = System.currentTimeMillis();
Future<String> 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.结论
根据用例,我们有各种选项来等待线程完成执行。
一个CountDownLatch当我们需要一种机制来通知一组由其他线程执行的操作已完成一个或多个线程是非常有用的。
当我们需要尽快访问任务结果时,ExecutorCompletionService非常有用,当我们想要等待所有正在运行的任务完成时,其他方法。
欢迎大家关注公众号:「Java知己」,关注公众号,回复「1024」你懂得,免费领取30本经典编程书籍。关注我,与10万程序员一起进步。每天更新Java知识哦,期待你的到来!