JDK并发包中ExecutorCompletionService使用

  相信大家都知道,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中,所以加入的顺序就是任务执行完成的先后顺序。

 

posted @ 2017-07-24 20:46  yehx  阅读(1058)  评论(0编辑  收藏  举报