【并发编程】Java5 - CompletionService,将异步执行与获取结果分离

1. 简介#

  相比Future(【并发编程】Java5 - Future,基本使用),CompletionService除了支持并行执行任务并获取结果外,还支持优先获取到最快执行的任务结果,但CompletionService要求并行执行的任务是无序的。
  使用Future的实现类FutureTask获取多个任务的结果时,当任务未执行完成,主线程会阻塞,只到任务执行完成才会获取到结果;CompletionService的实现类ExecutorCompletionService内部维护了一个存储Future的BlockingQueue,任务执行完成后会把Future保存到队列中。当获取多个任务的结果时,会从BlockingQueue中调用take()方法获取到Future再调用get()方法获取结果,如果暂时没有任务执行完成,则阻塞直到有任务执行完成并保存到BlockingQueue中,这样获取到结果的顺序会按照任务执行快慢的顺序依次返回。

2. CompletionService接口API#

方法及参数 描述
Future submit(Callable task) 提交实现Callable的线程任务
Future submit(Runnable task, V result) 提交实现Runnable的线程任务,并指定预期结果
Future take() 获取已完成任务的 Future,没有则阻塞
Future poll() 获取已完成任务的 Future,没有则返回null
Future poll(long timeout, TimeUnit unit) 在指定时间内获取已完成任务的 Future,没有则返回null

3. CompletionService使用场景#

  • 获取到任务结果后还需要继续处理,如果获取到结果后立即返回,则无法提现出CompletionService的优势
  • 多个任务并发执行,只需要获取到任意一个任务的结果即可

4. CompletionService应用#

  • 前期准备:实现Callable创建线程,支持指定执行时间
Copy
/** * 创建线程 */ @RequiredArgsConstructor class MyCallable implements Callable<Long> { /** * 任务执行时间 */ private final long execTime; @Override public Long call() throws Exception { Thread.sleep(execTime); return execTime; } }
  • Future:测试并行执行多个任务获取结果后继续业务处理
Copy
/** * Future:测试并行执行多个任务获取结果后继续业务处理 * * @throws InterruptedException * @throws ExecutionException */ @Test public void testFuture() throws InterruptedException, ExecutionException { ExecutorService executorService = Executors.newFixedThreadPool(5); List<Future<Long>> futureList = new ArrayList<>(3); // 记录开始时间 StopWatch stopWatch = new StopWatch(); stopWatch.start(); // 创建5个任务 futureList.add(executorService.submit(new MyCallable(5000))); futureList.add(executorService.submit(new MyCallable(4000))); futureList.add(executorService.submit(new MyCallable(3000))); futureList.add(executorService.submit(new MyCallable(2000))); futureList.add(executorService.submit(new MyCallable(1000))); // 阻塞获取结果 for (Future<Long> future : futureList) { Long result = future.get(); log.debug("执行结果:" + result); // 模拟获取结果后业务处理 Thread.sleep(500); } stopWatch.stop(); log.info("总耗时:" + stopWatch.getTotalTimeMillis() + " ms"); executorService.shutdown(); }

  控制台打印:

Copy
18:53:06.185 [main] DEBUG com.c3stones.test.CompletionServiceTest - 执行结果:5000 18:53:06.690 [main] DEBUG com.c3stones.test.CompletionServiceTest - 执行结果:4000 18:53:07.190 [main] DEBUG com.c3stones.test.CompletionServiceTest - 执行结果:3000 18:53:07.690 [main] DEBUG com.c3stones.test.CompletionServiceTest - 执行结果:2000 18:53:08.191 [main] DEBUG com.c3stones.test.CompletionServiceTest - 执行结果:1000 18:53:08.691 [main] INFO com.c3stones.test.CompletionServiceTest - 总耗时:7516 ms

  通过结果可以看出,结果按照提交的顺序依次返回,总耗时 = 每个任务执行时间 + 获取结果后业务处理时间。

  • CompletionService:测试并行执行多个任务获取结果后继续业务处理
Copy
/** * CompletionService:测试并行执行多个任务获取结果后继续业务处理 * * @throws InterruptedException * @throws ExecutionException */ @Test public void testCompletionService() throws ExecutionException, InterruptedException { ExecutorService executorService = Executors.newFixedThreadPool(5); CompletionService<Long> completionService = new ExecutorCompletionService<>(executorService); // 记录开始时间 StopWatch stopWatch = new StopWatch(); stopWatch.start(); // 创建5个任务 completionService.submit(new MyCallable(5000)); completionService.submit(new MyCallable(4000)); completionService.submit(new MyCallable(3000)); completionService.submit(new MyCallable(2000)); completionService.submit(new MyCallable(1000)); for (int i = 0; i < 5; i++) { Long result = completionService.take().get(); log.debug("执行结果:" + result); // 模拟获取结果后业务处理 Thread.sleep(500); } stopWatch.stop(); log.info("总耗时:" + stopWatch.getTotalTimeMillis() + " ms"); executorService.shutdown(); }

  控制台打印:

Copy
18:55:25.727 [main] DEBUG com.c3stones.test.CompletionServiceTest - 执行结果:1000 18:55:26.724 [main] DEBUG com.c3stones.test.CompletionServiceTest - 执行结果:2000 18:55:27.725 [main] DEBUG com.c3stones.test.CompletionServiceTest - 执行结果:3000 18:55:28.725 [main] DEBUG com.c3stones.test.CompletionServiceTest - 执行结果:4000 18:55:29.724 [main] DEBUG com.c3stones.test.CompletionServiceTest - 执行结果:5000 18:55:30.225 [main] INFO com.c3stones.test.CompletionServiceTest - 总耗时:5505 ms

  通过结果可以看出,结果按照提交的任务执行快慢顺序依次返回,总耗时 = 最长任务处理时间 + 获取结果后业务处理时间。

  • CompletionService:测试快速获取任意一个结果
Copy
/** * CompletionService:测试快速获取任意一个结果 */ @Test public void testFastResult() throws InterruptedException, ExecutionException { ExecutorService executorService = Executors.newCachedThreadPool(); CompletionService<Long> completionService = new ExecutorCompletionService<>(executorService); // 记录开始时间 StopWatch stopWatch = new StopWatch(); stopWatch.start(); // 创建3个任务 completionService.submit(new MyCallable(3000)); completionService.submit(new MyCallable(2000)); completionService.submit(new MyCallable(1000)); Long result = completionService.take().get(); log.debug("执行结果:" + result); stopWatch.stop(); log.info("总耗时:" + stopWatch.getTotalTimeMillis() + " ms"); executorService.shutdown(); }

  控制台打印:

Copy
18:59:58.104 [main] DEBUG com.c3stones.test.CompletionServiceTest - 执行结果:1000 18:59:58.112 [main] INFO com.c3stones.test.CompletionServiceTest - 总耗时:1126 ms

  通过结果可以看出,最快的任务执行完成后结束。

5. 项目地址#

  thread-demo

posted @   C3Stones  阅读(123)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· winform 绘制太阳,地球,月球 运作规律
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
历史上的今天:
2017-03-28 蓝桥杯 历届试题 PREV-1 核桃的数量
点击右上角即可分享
微信分享提示
CONTENTS