CompletionService的介绍
CompletionService用于提交一组Callable任务,其take方法返回已完成的一个Callable任务对应的Future对象。
如果你向Executor提交了一个批处理任务,并且希望在它们完成后获得结果。为此你可以将每个任务的Future保存进一个集合,然后循环这个集合调用Future的get()取出数据。幸运的是CompletionService帮你做了这件事情。
CompletionService整合了Executor和BlockingQueue的功能。你可以将Callable任务提交给它去执行,然后使用类似于队列中的take和poll方法,在结果完整可用时获得这个结果,像一个打包的Future。
CompletionService的take返回的future是哪个先完成就先返回哪一个,而不是根据提交顺序。
CompletionService的使用
针对上面的需求“如果你向Executor提交了一个批处理任务,并且希望在它们完成后获得结果。”
可以有一下两种实现方式
方式一:自己维护一个Collection保存submit方法返回的Future存根,然后在主线程中遍历这个Collection并调用Future存根的get()方法取到线程的返回值。
方式一实现
public class collection_future_Test {
public static void main(String[] args){
int taskSize = 10;
ExecutorService executor = Executors.newFixedThreadPool(taskSize);
List<Future<Integer>> futureList = new ArrayList<Future<Integer>>();
for(int i= 1; i<=taskSize; i++){
int sleep = taskSize -1;
int value = i;
//向线程池提交任务
Future<Integer> future = executor.submit(new TestThread(sleep, value));
//保留每个任务的Future
futureList.add(future);
}
// 获取完成任务的返回结果
// 检查线程池任务执行结果
for (int i = 1; i <=taskSize; i++) {
try {
Future<Integer> future = futureList.get(i);
System.out.println("result = "+result);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
// 关闭线程池
// 所有任务已经完成,关闭线程池
System.out.println("all over ");
executor.shutdown();
}
public class TestThread implements Callable<Integer> {
private int sleep;
private int Value;
public TestThread(int Seconds,int Value){
this.sleep = Seconds;
this.Value = Value;
}
@Override
public Integer call() throws Exception {
System.out.println("begin to execute "+ Thread.currentThread().getName());
TimeUnit.SECONDS.sleep(sleep);
return Value;
}
}
这种方式的问题是:用所创建的集合来保存Future存根并循环调用其返回结果的时候,主线程并不能保证首先获得的是最先完成任务的线程返回值。它只是按加入线程池的顺序返回。因为take方法是阻塞方法,后面的任务完成了,前面的任务却没有完成,主程序就那样等待在那儿,只到前面的完成了,它才知道原来后面的也完成了
方式二:使用CompletionService类,它整合了Executor和BlockingQueue的功能。你可以将Callable任务提交给它去执行,然后使用类似于队列中的take方法获取线程的返回值。
方式二的实现:
public class CompletionServiceTest {
public static void main(String[] args){
int taskSize = 5;
ExecutorService executor = Executors.newFixedThreadPool(taskSize);
// 构建完成服务
CompletionService<Integer> completionService = new ExecutorCompletionService<Integer>(executor);
for (int i=1;i<= taskSize; i++){
// 睡眠时间
int sleep = taskSize - i;
// 返回结果
int value = i;
//向线程池提交任务
completionService.submit(new TestThread(sleep, value));
try {
System.out.println("result:"+completionService.take().get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
System.out.println("all over. ");
executor.shutdown();
}
}
public class TestThread implements Callable<Integer> {
private int sleep;
private int Value;
public TestThread(int Seconds,int Value){
this.sleep = Seconds;
this.Value = Value;
}
@Override
public Integer call() throws Exception {
System.out.println("begin to execute "+ Thread.currentThread().getName());
TimeUnit.SECONDS.sleep(sleep);
return Value;
}
}
总结
方法一,自己创建一个集合来保存Future存根并循环调用其返回结果的时候,主线程并不能保证首先获得的是最先完成任务的线程返回值。它只是按加入线程池的顺序返回。因为take方法是阻塞方法,后面的任务完成了,前面的任务却没有完成,主程序就那样等待在那儿,只到前面的完成了,它才知道原来后面的也完成了。
方法二,使用CompletionService来维护处理线程不的返回结果时,主线程总是能够拿到最先完成的任务的返回值,而不管它们加入线程池的顺序。