如果向Executor提交了一组计算任务,并且希望在计算完成后获得结果,那么可以保留与每个任务关联的Future,然后反复使用get方法,同时将参数Timeout指定为0,从而通过轮询来判断任务是否完成。这种方法虽然可行,但确繁琐,可以使用CompletionService完成服务。
CompletionService接口定义了5个方法,并且该接口有且只有一个实现类:ExecutorCompletionService。
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定义了一个线程安全的队列:completionQueue
private final BlockingQueue<Future<V>> completionQueue;
以及一个私有类:QueueingFuture:
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;
}
这两个东西,就可以看成是CompletionService功能的核心。
QueueingFuture类重写了FutureTask的done()方法,当一个FutureTask完成的时候,就会将该任务放入完成队列completionQueue里,然后通过队列的take()或poll()方法,将已完成的Future取出。
流程如下:
当使用CompletionService提交一个Callable时,会将该Callable包装成一个FutureTask,最终变成一个QueueingFuture类,然后直接执行该QueueingFuture:
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;
}
private RunnableFuture<V> newTaskFor(Callable<V> task) {
if (aes == null)
return new FutureTask<V>(task);
else
return aes.newTaskFor(task);
}
由于QueueingFuture继续了FutureTask并重写了done方法,所以当QueueingFuture执行完成后,会调用其done()方法,也就是将该完成的Future放入任务完成队列里。
然后可以通过take()或poll()方法取出该已完成的Future。(poll()是非阻塞的,若目前无结果,返回一个null,线程继续运行不阻塞。take()是阻塞的,若当前无结果,则线程阻塞,直到产生一个结果,被取出返回,线程才继续运行。)
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提交若干个任务,哪个任务先完成,就先取哪个任务。