相信大家都知道,jdk中ExecutorService是并发编程时使用很频繁的接口,并且使用很方便,那么想在有这么一个场景:
一批任务使用线程池处理,并且需要获得结果,但是不关心任务执行结束后输出结果的先后顺序,应该如何实现?大多数人可能会想到,将任务作为一个Callable,然后调用submit塞入ExecutorService中,返回Future,然后遍历Future,依次获得结果不就行了吗?
那么,大家是否想过,这样有两个不好的点:1.调用submit后需要将Future存储;2.遍历Future的list时,get()方法时阻塞的,就算使用get(long timeout, TimeUnit unit)方法,避免不了需要通过while来循环获取结果
其实如果不关心任务返回的先后顺序,那么还有一个更方便的线程池,也就是今天要分享,使用ExecutorCompletionService避免以上两个问题,并且编码简单,容易理解:
public class CompletionServiceTest {
//初始化固定大小为3的线程池
private static ExecutorService executor = Executors.newFixedThreadPool(3); public static void main(String[] args) {
List<CompletionServiceTask> tasks = new ArrayList<>();
//新建3个任务
CompletionServiceTask task1 = new CompletionServiceTask(6000);
CompletionServiceTask task2 = new CompletionServiceTask(4000);
CompletionServiceTask task3 = new CompletionServiceTask(2000);
tasks.add(task1);
tasks.add(task2);
tasks.add(task3);
addTask(tasks);
} public static void addTask(List<CompletionServiceTask> tasks) {
//以executor为构造器的参数,新建一个ExecutorCompletionService线程池
ExecutorCompletionService<Integer> completionService = new ExecutorCompletionService<>(executor);
for (CompletionServiceTask task : tasks) {
//提交任务
completionService.submit(task);
System.out.println("添加任务 :" + task.time);
}
for (CompletionServiceTask task : tasks) {
try {
Integer time = completionService.take().get();
System.out.println("任务返回结果:" + time);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
//关闭线程池
executor.shutdown();
} static class CompletionServiceTask implements Callable<Integer> { public int time; public CompletionServiceTask(int time) {
this.time = time;
} @Override
public Integer call() throws Exception {
Thread.sleep(time);
return time;
}
}
}
上述代码运行结果:
添加任务 :6000
添加任务 :4000
添加任务 :2000
任务返回结果:2000
任务返回结果:4000
任务返回结果:6000
从结果可以看出,虽然6000是第一个添加进去,但是最先返回的确实2000,因此我们可以看出,并非先添加的任务先返回,而是最先执行结束的任务先返回,这样的好处就是不用为了等待前面的任务,导致后续的阻塞。
原因分析:查看ExecutorCompletionService的源码:
private final BlockingQueue<Future<V>> completionQueue;
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;
}
其中,有一个类型为BlockingQueue的全局变量completionQueue(这里划重点,这个阻塞队列就是用来存储线程池执行返回结果的),submit()方法会将Callable封装成一个RunnableFuture,然后将其塞入QueueingFuture中,交给executor执行,我们再看一下QueueingFuture(FutureTask的一个子类):
private final BlockingQueue<Future<V>> 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;
}
其通过改写FutureTask类的done()方法,将结果放入上面的BlockingQueue中,所以加入的顺序就是任务执行完成的先后顺序。