并发编程-ExecutorCompletionService解析
内容提要
ExecutorCompletionService是并发编程中的工具,用于确保最先完成的任务首先被处理,而不考虑任务添加到线程池的顺序。它实现了CompletionService接口,提供了提交任务和检索结果的方法。实现方式是将提交的任务包装在QueueingFuture对象中,并在指定的线程池中执行。一旦任务完成,其QueueingFuture对象将RunnableFuture对象添加到阻塞队列中。可以使用poll或take方法从队列中获取结果。ExecutorCompletionService在任务提交和结果检索的顺序不重要的场景中非常有用。文章中提供了此类场景的示例。在一个实际的例子中,ExecutorCompletionService被用于并行更新一批订单,并实时更新进度。
延伸解读
与遍历Future集合的差异
传统做法是将所有任务提交到线程池后,遍历Future集合调用get(),这会按提交顺序等待结果,可能因某个慢任务而阻塞后续已完成任务的结果处理。ExecutorCompletionService通过内部阻塞队列,将已完成任务按完成顺序放入队列,take()或poll()获取的是最先完成的任务,从而避免不必要的等待,提升整体处理效率。
构造参数与队列选择
ExecutorCompletionService有两个构造方法:一个仅接收线程池,内部创建LinkedBlockingQueue;另一个允许传入自定义阻塞队列。在业务代码中,可以指定队列容量,例如示例中根据任务总数设置容量,避免无界队列可能带来的内存问题。同时,传入的线程池决定了任务的实际执行方式,若线程池是AbstractExecutorService,还会复用其newTaskFor方法创建FutureTask。
适用场景与注意事项
该工具适用于不关心任务提交顺序、希望先完成先处理的场景,也适用于只需获取第一个成功结果的场景。但需注意:take()会阻塞直到有结果,poll()可非阻塞或带超时;若任务抛出异常,get()时会抛出ExecutionException,需妥善处理。在只需第一个结果的示例中,获取到结果后应取消其余任务,避免资源浪费。
业务实践:并行更新订单进度
在批量更新订单的业务中,每完成一单都需要实时更新进度。使用ExecutorCompletionService,每处理完一个订单,其Future会被放入队列,主线程通过take().get()获取并更新进度,确保进度数据及时反映最新完成情况。代码中通过finally块调用jimClient.incr(importTaskNo)来递增进度,保证了即使获取结果异常也能更新进度,体现了该工具在实时进度维护上的实用性。
Q&A
ExecutorCompletionService的主要功能是什么?
ExecutorCompletionService用于确保最先完成的任务首先被处理,而不考虑任务添加到线程池的顺序。
如何使用ExecutorCompletionService提交任务?
可以通过submit方法提交Callable或Runnable任务,任务会被包装成QueueingFuture对象并由线程池执行。
ExecutorCompletionService适合在哪些场景使用?
适合在不关注任务提交顺序和结果顺序的场景,或只需获取第一个完成任务结果的情况。
ExecutorCompletionService如何获取任务结果?
可以使用poll或take方法从阻塞队列中获取任务结果,先完成的任务会先被处理。
ExecutorCompletionService的构造方法需要哪些参数?
构造方法需要传入一个线程池和可选的阻塞队列。
ExecutorCompletionService在实际业务中有什么应用示例?
在实际业务中,可以用于并行更新订单并实时更新处理进度。