开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第4天,点击查看活动详情
ExecutorCompletionService使用场景
ExecutorCompletionService主要用于管理异步任务 (有结果的任务, 任务完成后要处理结果)。
使用多线程进行并行处理多个的时候,正常场景在处理多线程并行执行任务并将Future结果添加List,多个任务的执行结果会按照提交顺序的任务执行时间进行堵塞依次获取结果;通过Futrue类的get()方法,会造成堵塞,需要先等执行任务1的线程结束返回结果,才会进行获取下一个任务的执行的结果,那边后面的任务先于任务一执行结束;当然如果工作中我们不需要获取多个任务执行的结果,我们可以采用上面的实现方式去进行并行处理任务;如果我们要获取到并行处理任务的结果快慢来进行一些处理,我们就可以使用到ExecutorCompletionService来进行实现;可以使用ExecutorCompletionService类将线程池进行包装处理,然后进行提交任务。
// 注入线程池
@Autowired
private ThreadPoolTaskExecutor threadPoolTaskExecutor;
public void save() {
// 这里模拟15万条数据
List<AggregateAccounting> list = this.list();
// 注入线程池
ExecutorCompletionService<Integer> completionService = new ExecutorCompletionService<Integer>(
threadPoolTaskExecutor);
//partition()分组处理
List<List<AggregateAccounting>> lists = Lists.partition(list, 3000);
lists.forEach(item -> {
//根据lists大小确认要多少个线程 给每个线程分配任务
completionService.submit(new Callable() {
@Override
public Object call() throws Exception {
return insertList(item);
}
});
});
// 这里是让多线程开始执行
lists.forEach(item -> {
try {
completionService.take().get();
} catch (InterruptedException | ExecutionException e) {
System.out.println(e);
}
});
}
ExecutorCompletionService常用方法
ExecutorCompletionService类是CompletionService接口的实现。CompletionService接口定义了一组任务管理接口:
- submit() - 提交任务
- take() - 获取任务结果
- poll() - 获取任务结果
ExecutorCompletionService内部管理者一个已完成任务的阻塞队列;ExecutorCompletionService引用了一个Executor, 用来执行任务;submit()方法最终会委托给内部的executor去执行任务;take/poll方法的工作都委托给内部的已完成任务阻塞队列;如果阻塞队列中有已完成的任务, take方法就返回任务的结果, 否则阻塞等待任务完成。
-
poll与take方法不同, poll有两个版本:
- 无参的poll方法 --- 如果完成队列中有数据就返回用于否则返回null
- 有参数的poll方法 --- 如果完成队列中有数据就直接返回, 否则等待指定的时间, 到时间后如果还是没有数据就返回null
ExecutorCompletionService是如何执行任务, 又是如何将任务的结果存储到完成队列中的呢?
ExecutorCompletionService在submit任务时, 会创建一个QueueingFuture, 然后将创建的QueueingFuture丢给executor, 让executor完成任务的执行工作。
QueueingFuture继承与FutureTask类, 而FutureTask实现了两个接口Runnable和Future。
Runnable一般表示要执行的任务的过程, 而Future则表述执行任务的 结果 (或者说是任务的一个句柄, 可获取结果, 取消任务等)。
因此FutureTask就是一个有结果可期待的任务. FutureTask实现了run方法。
FutureTask构造的时候需要一个Callable参数, Callable表示一个任务的执行过程, 在run方法中恰好调用了Callable.call(), 也就是任务工作在工作线程中执行。
那么任务执行完了会返回结果, 这个结果是要在submit线程(就是提交任务的线程)中使用的, 那么如何让submit线程可以反问到呢? 答案也是在FutureTask类中, 我们可以看到run方法中执行任务(Callable.call())获取结果后, 会调用一个set()方法。
set() 将获取的结果存储到FuturnTask的一个outcome字段中, 这个过程是同步的, 所以其他线程稍后访问是可以读取到值的。
ExecutorCompletionService中的完成队列中正好存储的是FuturnTask的子类, 当然可以调用FutureTask的get方法, FutureTask的get方法就是获取outcome值 (get()方法中调用了report()方法, report中返回了outcome字段)。