假设我们要向线程池提交一批任务,并获取任务结果。一般的方式是提交任务后,从线程池得到一批 Future 对象集合,然后依次调用其 get() 方法。
这里有个问题:因为我们会要按固定的顺序来遍历 Future 元素,而 get() 方法又是阻塞的,因此如果某个 Future 对象执行时间太长,会使得我们的遍历过程阻塞在该元素上,无法及时从后面早已完成的 Future 当中取得结果。
CompletionService 解决了这个问题,可以获取先执行完毕的任务。
成员属性
public class ExecutorCompletionService<V> implements CompletionService<V> {
private final Executor executor;
private final AbstractExecutorService aes;
private final BlockingQueue<Future<V>> completionQueue;
}
CompletionService 的实现原理也是内部维护了一个阻塞队列,当任务执行结束就把任务的执行结果的 Future 对象加入到阻塞队列中。
下面是一个例子
//它本身不包含线程池,创建一个 CompletionService 需要先创建一个 Executor。
CompletionService<Integer> completionService = new ExecutorCompletionService<>(asyncExecutor);
int sendCount = 0;
for (Integet item : list) {
sendCount++;
completionService.submit(() -> {
return item;
});
}
try {
for (int i = 0; i < sendCount; i++) {
//阻塞等待最先的返回结果
completionService.take();
}
} catch (InterruptedException e) {
logger.error("等待返回结果异常", e);
}
CompletionService 之所以能够做到这点,是因为它没有采取依次遍历 Future 的方式,而是在中间加上了一个结果队列,任务完成后马上将结果放入队列,那么从队列中取到的就是最早完成的结果。
如果队列为空,那么 take() 方法会阻塞直到队列中出现结果为止。此外 CompletionService 还提供一个 poll() 方法,返回值与 take() 方法一样,不同之处在于它不会阻塞,如果队列为空则立刻返回 null。这算是给用户多一种选择。
take()
取走BlockingQueue里排在首位的对象,若BlockingQueue为空,阻断进入等待状态直到BlockingQueue有新的数据被加入;
poll(long timeout, TimeUnit unit)
从BlockingQueue取出一个队首的对象,如果在指定时间内,队列一旦有数据可取,则立即返回队列中的数据。否则知道时间
参数:
timeout - 放弃前等待多长时间,以 unit 为unit
unit – 一个TimeUnit决定如何解释timeout参数
返回值:
此队列的头部,如果在元素可用之前经过了指定的等待时间,则为null