应用场景
当向Executor提交多个任务并且希望获得它们在完成之后的结果,如果用FutureTask,可以循环获取task,并调用get方法去获取task执行结果,但是如果task还未完成,获取结果的线程将阻塞直到task完成,由于不知道哪个task优先执行完毕,使用这种方式效率不会很高。在jdk5时候提出接口CompletionService,它整合了Executor和BlockingQueue的功能,可以更加方便在多个任务执行时获取到任务执行结果。
案例
需求:不使用求和公式,计算从1到100000000相加的和。
分析设计:需求指明不能使用求和公式,只能循环依次相加,为了提高效率,我们可以将1到100000000的数分为n段由n个task执行,执行结束后merge结果求最后的和。
代码实现:
public class CompletionServiceTest {
private static final int THREAD_NUM = 10;
private static ExecutorService executor = Executors.newFixedThreadPool(THREAD_NUM);
@Test
public void test() {
CompletionService<Long> completionService = new ExecutorCompletionService<>(executor);
final int groupNum = 100000000 / THREAD_NUM;
for(int i=1; i<=THREAD_NUM; i++) {
int start = (i - 1) * groupNum + 1, end = i * groupNum;
completionService.submit(new Callable<Long>() {
@Override
public Long call() throws Exception {
long sum = 0L;
for(int j = start; j <= end; j++) {
sum += j;
}
return sum;
}
});
}
long result = 0L;
try {
for(int i=1; i<=THREAD_NUM; i++) {
result += completionService.take().get();
System.out.println(result);
}
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("the result is " + result);
}
}
运行结果:
250000005000000
400000010000000
450000015000000
800000020000000
1250000025000000
1800000030000000
2450000035000000
3200000040000000
4050000045000000
5000000050000000
the result is 5000000050000000
由上面的运行结果,我们可以看到,先提交的任务不一定先执行完成。
实现原理
CompletionService接口
CompletionService接口定义了一系列任务提交和在任务完成后获取任务相关的Future对象的方法
相关方法定义如下:
方法 | 描述 |
---|---|
Future submit(Callable task) | 提交一个返回执行结果的任务,方法返回一个封装任务执行情况的Future对象 |
Future submit(Runnable task, V result) | 提交一个Runnable的任务,方法返回一个封装任务执行情况的Future对象 |
Future take() throws InterruptedException | 获取并移除代表下一个完成的任务的Future对象,如果当前还没有完成的任务,则等待 |
Future poll() | 获取并移除代表下一个完成的任务的Future对象,如果当前还没有完成的任务,则返回null |
Future poll(long timeout, TimeUnit unit) throws InterruptedException | 获取并移除代表下一个完成的任务的Future对象,如果当前还没有完成的任务,则等待指定的时间。如果在指定的时间内任务执行完成,则返回相应的Future对象;否则,返回null |
ExecutorCompletionService 类
ExecutorCompletionService 类实现了CompletionService接口,给出了具体的实现。
ExecutorCompletionService 使用 Executor 来执行任务,任务执行完成后将任务相关的 Future 对象放入队列中。
外部可以使用 take,poll 等方法来获取到执行的结果。
private final Executor executor;
private final AbstractExecutorService aes;
private final BlockingQueue<Future<V>> completionQueue;
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>>();
}
public ExecutorCompletionService(Executor executor,
BlockingQueue<Future<V>> completionQueue) {
if (executor == null || completionQueue == null)
throw new NullPointerException();
this.executor = executor;
this.aes = (executor instanceof AbstractExecutorService) ?
(AbstractExecutorService) executor : null;
this.completionQueue = completionQueue;
}
submit 方法相关实现
submit 方法受限将传入的 Callable 或者 Runnable 对象封装成 RunnableFuture 对象,然后使用 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;
}
这里 executor 执行任务之前先将任务封装成了 QueueingFuture 类的实例。QueueingFuture继承自FutureTask类,覆盖了 done 方法,当任务执行完成时,将任务相关的RunnableFuture对象写入队列。
/**
* FutureTask extension to enqueue upon completion
*/
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;
}
take, poll
take 方法和 poll 方法的实现就比较简单了。直接调用队列的相应方法。
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);
}