上一篇讲了ExecutorService关于任务的异步执行和状态控制的部分,这篇说说关于任务批量执行的部分。ExecutorSerivce中关于批量执行的接口如下
<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)
throws InterruptedException;
<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks,
long timeout, TimeUnit unit)
throws InterruptedException;
<T> T invokeAny(Collection<? extends Callable<T>> tasks)
throws InterruptedException, ExecutionException;
<T> T invokeAny(Collection<? extends Callable<T>> tasks,
long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException;
invokeAll接口返回一个Future集合,包含了所有的任务的Future对象。invokeAny这个接口输入一组任务,返回任意一个成功执行的任务的结果,其他的任务被取消。
AbstractExecutorService给出了invokeAll和invokeAny的默认实现。通过代码可以看到ExecutorService实现的任务批量执行的逻辑和一些问题。
invokeAll的实现如下:
1. 给每个任务创建RunnableTask结构,这个类是FutureTask的实现类,然后扔给线程池去执行execute。
2. 轮询Future集合,如果任务没有执行完成!f.done(),就调用FutureTask.get()方法,这个方法会阻塞等待直到任务完成或者抛出异常
3. 当所有任务都执行完成后才返回结果
invokeAll的问题显然易见,就是必须等待所有任务完成才返回,任务执行期间是无法获得结果的,如果有些任务耗时很长,有些任务耗时很短,那么先完成的任务也只能全部任务完成后才能返回。
public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)
throws InterruptedException {
if (tasks == null)
throw new NullPointerException();
List<Future<T>> futures = new ArrayList<Future<T>>(tasks.size());
boolean done = false;
try {
for (Callable<T> t : tasks) {
RunnableFuture<T> f = newTaskFor(t);
futures.add(f);
execute(f);
}
for (Future<T> f : futures) {
if (!f.isDone()) {
try {
f.get();
} catch (CancellationException ignore) {
} catch (ExecutionException ignore) {
}
}
}
done = true;
return futures;
} finally {
if (!done)
for (Future<T> f : futures)
f.cancel(true);
}
}
正是ExecutorService在处理批量任务时必须等待全部任务都完成才能返回结果的问题,引入了CompletionService接口。CompletionService提供了一个完成队列来解决这个问题。看一下CompletionService的接口定义。可以看到它的功能分为两部分
1. submit提交任务,返回Future,进行异步任务的状态控制
2. take, poll 这两个队列操作,前者是阻塞队列操作,后者可以快速返回,也可以限时操作
CompletionService的take, poll这两个方法就是对它的完成队列进行操作,完成的任务进入完成队列,可以被直接获取,不用等待其他任务的完成。
public interface CompletionService<V> {
Future<V> submit(Callable<V> task);
Future<V> submit(Runnable task, V result);
Future<V> take() throws InterruptedException;
Future<V> poll();
Future<V> poll(long timeout, TimeUnit unit) throws InterruptedException;
}
ExecutorCompletionService实现了CompletionService接口。它有3个属性
1. private final Executor executor; 实际执行任务的Executor
2. private final AbstractExecutorService aes; 使用它的newTaskFor方法来适配Runnable和Callable任务,统一返回RunnableFuture结构
3. private final BlockingQueue<Future<V>> completionQueue; 存放执行完成的任务的完成队列,是一个阻塞队列
内部类QueueingFuture继承了FutureTask,它的目的是重写FutureTask的done方法,将完成的任务自动放入完成队列completionQueue
private class QueueingFuture extends FutureTask<Void> {
QueueingFuture(RunnableFuture<V> task) {
super(task, null);
this.task = task;
}
protected void done() { completionQueue.add(task); }
private final Future<V> task;
}
ExecutorCompletionService默认的构造函数里使用了LinkedBlockingQueue来作阻塞队列
public ExecutorCompletionService(Executor executor) {
if (executor == null)
throw new NullPointerException();
this.executor = executor;
this.aes = (executor instanceof AbstractExecutorService) ?
(AbstractExecutorService) executor : null;
this.completionQueue = new LinkedBlockingQueue<Future<V>>();
}
它的submit方法很简单,就是把提交的任务封装成QueueingFuture,然后交给Executor执行,
public Future<V> submit(Callable<V> task) {
if (task == null) throw new NullPointerException();
RunnableFuture<V> f = newTaskFor(task);
executor.execute(new QueueingFuture(f));
return f;
}
public Future<V> submit(Runnable task, V result) {
if (task == null) throw new NullPointerException();
RunnableFuture<V> f = newTaskFor(task, result);
executor.execute(new QueueingFuture(f));
return f;
}
take和poll方法也很简单,直接交给完成队列completionQueue来执行阻塞队列的操作
public Future<V> take() throws InterruptedException {
return completionQueue.take();
}
public Future<V> poll() {
return completionQueue.poll();
}
public Future<V> poll(long timeout, TimeUnit unit)
throws InterruptedException {
return completionQueue.poll(timeout, unit);
}
看了CompletionService的实现,来看一下如何使用它。AbstractExecutorService的doInvokeAny方法使用了CompletionService。
invokeAny方法是提交一组任务,然后有一个执行成功的任务就可以返回结果,然后取消其他任务。
1. List<Future<T>> futures= new ArrayList<Future<T>>(ntasks); 创建一个Future集合来存放任务的Future
2. ExecutorCompletionService<T> ecs = new ExecutorCompletionService<T>(this); 创建一个ComletionService
3. futures.add(ecs.submit(it.next())); 先提交一个任务
4. 在无限循环中,先看一下任务执行结果 Future<T> f = ecs.poll();
5. 如果f != null,表示已经有任务完成,然后调用f.get去取结果,如果能取到,就直接返回结果。如果抛出异常,则继续循环
6. 如果f == null,表示任务没有完成,就再提交一个任务。如果是限时操作,就计算一下时间,判断是否超时
public <T> T invokeAny(Collection<? extends Callable<T>> tasks)
throws InterruptedException, ExecutionException {
try {
return doInvokeAny(tasks, false, 0);
} catch (TimeoutException cannotHappen) {
assert false;
return null;
}
}
private <T> T doInvokeAny(Collection<? extends Callable<T>> tasks,
boolean timed, long nanos)
throws InterruptedException, ExecutionException, TimeoutException {
if (tasks == null)
throw new NullPointerException();
int ntasks = tasks.size();
if (ntasks == 0)
throw new IllegalArgumentException();
List<Future<T>> futures= new ArrayList<Future<T>>(ntasks);
ExecutorCompletionService<T> ecs =
new ExecutorCompletionService<T>(this);
// For efficiency, especially in executors with limited
// parallelism, check to see if previously submitted tasks are
// done before submitting more of them. This interleaving
// plus the exception mechanics account for messiness of main
// loop.
try {
// Record exceptions so that if we fail to obtain any
// result, we can throw the last exception we got.
ExecutionException ee = null;
long lastTime = timed ? System.nanoTime() : 0;
Iterator<? extends Callable<T>> it = tasks.iterator();
// Start one task for sure; the rest incrementally
futures.add(ecs.submit(it.next()));
--ntasks;
int active = 1;
for (;;) {
Future<T> f = ecs.poll();
if (f == null) {
if (ntasks > 0) {
--ntasks;
futures.add(ecs.submit(it.next()));
++active;
}
else if (active == 0)
break;
else if (timed) {
f = ecs.poll(nanos, TimeUnit.NANOSECONDS);
if (f == null)
throw new TimeoutException();
long now = System.nanoTime();
nanos -= now - lastTime;
lastTime = now;
}
else
f = ecs.take();
}
if (f != null) {
--active;
try {
return f.get();
} catch (ExecutionException eex) {
ee = eex;
} catch (RuntimeException rex) {
ee = new ExecutionException(rex);
}
}
}
if (ee == null)
ee = new ExecutionException();
throw ee;
} finally {
for (Future<T> f : futures)
f.cancel(true);
}
}