读《Java并发编程实战》6.3.5 笔记。
如果向 Executor 提交了一组计算任务,并且希望在计算完成后获得结果,那么可以保留与每个任务的关联的 Future,然后反复使用 get 方法,同时将参数 timeout 指定为 0,从而通过轮询来判断任务是否完成。
这种方法虽然可行,但却有些繁琐。幸运的是,还有一种更好的方法:完成服务 (CompletionService
)
说明:
-
CompletionService 将 Executor 和 BlockingQueue 的功能融合在一起。
-
你可以将 Callable 任务提交给它来执行,然后使用类似于队列操作的 take 和 poll 等方法来获得已完成的结果,而这些结果会在完成时被封装为 Future。
-
ExecutorCompletionService 实现了 CompletionService,并将计算部分委托给一个 Executor。
ExecutorCompletionService
ExecutorCompletionService 的实现非常简单。
-
在构造函数中创建一个 BlockingQueue 来保存计算完成的结果。
-
当计算完成时,调用 FutureTask 中的 done 方法。
-
当提交某个任务时,
- 该任务将首先包装为一个 QueueingFuture,这是 FutureTask 的一个子类,
- 然后改写子类的 done 方法,并将结果放入 BlockingQueue中。
源码:
新建:
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;
// 在构造函数中创建一个 BlockingQueue 来保存计算完成的结果
this.completionQueue = completionQueue;
}
public ExecutorCompletionService(Executor executor) {
if (executor == null)
throw new NullPointerException();
this.executor = executor;
this.aes = (executor instanceof AbstractExecutorService) ?
(AbstractExecutorService) executor : null;
// 在构造函数中创建一个 BlockingQueue 来保存计算完成的结果
this.completionQueue = new LinkedBlockingQueue<Future<V>>();
}
提交任务:
public Future<V> submit(Callable<V> task) {
if (task == null) throw new NullPointerException();
RunnableFuture<V> f = newTaskFor(task);
// 包装一个 QueueingFuture
executor.execute(new QueueingFuture(f));
return f;
}
private class QueueingFuture extends FutureTask<Void> {
QueueingFuture(RunnableFuture<V> task) {
super(task, null);
this.task = task;
}
// 重写 FutureTask 的 done,将结果放入 BlockingQueue中
protected void done() { completionQueue.add(task); }
private final Future<V> task;
}
获取结果:
public Future<V> take() throws InterruptedException {
// 利用 BlockingQueue 队列来获取结果
return completionQueue.take();
}
完整使用例子
import java.util.Random;
import java.util.concurrent.Callable;
import java.util.concurrent.CompletionService;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorCompletionService;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
/**
* {@link ExecutorCompletionService}测试类
* <p>
*
* @author hyl
* @version v1.0: CompletionServiceTest.java, v 0.1 2020/10/13 18:03 $
*/
public class CompletionServiceTest {
static Random random = new Random();
public static void main(String[] args) throws InterruptedException, ExecutionException {
ExecutorService executorService = Executors.newFixedThreadPool(10);
CompletionService<Integer> completionService = new ExecutorCompletionService<>(executorService);
for (int i = 0; i < 10; i++) {
completionService.submit(new UpdateRunnable());
}
for (int i = 0; i < 10; i++) {
Future<Integer> take = completionService.take();
int time = take.get();
System.out.println(Thread.currentThread().getName()+":run "+time+"s");
}
executorService.shutdown();
}
static class UpdateRunnable implements Callable<Integer> {
@Override
public Integer call() throws Exception {
int time = random.nextInt(8);
System.out.println(Thread.currentThread().getName()+":run "+time+"s");
TimeUnit.SECONDS.sleep(time);
return time;
}
}
}
- 多个
ExecutorCompletionService
可以共享一个Executor
,因此可以创建一个对于特定计算私有,又能共享也给公共Executor
的ExecutorCompletionService
。- 因此,
CompletionService
的作用就相当于一组计算的句柄,这与Future
作为单个计算的句柄是非常类似的。- 通过记录提交给
CompletionService
的任务数量,并计算出已经获取的已完成结果的数量,即使使用一个共享的Executor
,也能知道已经获得了所有任务结果的时间。