ExecutorService和ExecutorCompletionService的简单使用区别-二改

既然是简单的区别,那么就直接上代码吧

包装返回值

	//简单返回结果
static class Result<T>{
        T o;
        public Result(T t) {
            o=t;
        }
    }

ExecutorService 操作模式

	/**
     * ExecutorService 操作模式
     *
     * 可主动设置return返回值,等待所有的结果集返回...
     */
    public static void doExecutor(ExecutorService executor,List<Callable<Result<String>>> list) {
        ArrayList<FutureTask<Result<String>>> futureTasks=new ArrayList<>();
        for(Callable<Result<String>> callable: list){
            FutureTask<Result<String>> futureTask=  (FutureTask<Result<String>>)executor.submit(
                    callable
            );
            futureTasks.add(futureTask);
        }
        for (FutureTask<Result<String>> task : futureTasks) {
            try {
                Result<String> result=task.get();//阻塞式
                task.get(1000,TimeUnit.SECONDS);//阻塞指定时间式 ->抛出TimeoutException
                //do result
            } catch (InterruptedException | ExecutionException | TimeoutException e) {
                //捕获异常 InterruptedException todo other things
                //捕获异常 TimeoutException todo other things
                //捕获异常 ExecutionException 执行异常,线程可能被中断 抛出?
                e.printStackTrace();
            }
        }
    }

ExecutorCompletionService<> 操作模式

	/**
     * ExecutorCompletionService<> 操作模式
     *
     * 可主动设置return返回值,等待所有的结果集返回...
     */
    public static void doExecutorCompletionService(Executor executor,List<Callable<Result<String>>> list) {
        ExecutorCompletionService<Result<String>> service=new ExecutorCompletionService<>(executor);//根据个人实现可以选择其他形式
        for(Callable<Result<String>> callable: list){
            //可不用主动收集返回值,在内部维护了阻塞队列,用以存储FutureTask的结果放入至队列中
            service.submit(
                    callable
            );
        }
        for (int i = 0; i < list.size(); i++) {
            try {
                //阻塞式   正常来说,线程执行结束,task状态已经变更为NORNAL才会执行done,
                // 这个时候才会将结果集放入至阻塞队列中,在此之前take一直阻塞 ,同时get方法就直接返回
                Future<Result<String>> task = service.take();
                service.poll();//非阻塞 立即返回结果
                service.poll(1000,TimeUnit.SECONDS);//等待指定时间式

                Result<String> result=task.get();//此时就不用阻塞直接返回结果集
                //do result
            } catch (InterruptedException | ExecutionException e) {
                //捕获异常 InterruptedException todo other things
                //捕获异常 ExecutionException 执行异常,线程可能被中断 抛出?
                e.printStackTrace();
            }
        }
    }

main

public static void main(String[] args) {
        ExecutorService executor = Executors.newFixedThreadPool(10);
        List<Callable<Result<String>>> callableList=new ArrayList<>();
        callableList.add(()-> new Result<>("1"));
        //ExecutorService 操作模式
        doExecutor(executor,callableList);

        List<Callable<Result<String>>> callableList1=new ArrayList<>();
        callableList1.add(() -> new Result<>("2"));
		//ExecutorCompletionService<> 操作模式
        doExecutorCompletionService(executor,callableList1);


       
    }

区别

  • ExecutorCompletionService<> 操作模式 仅仅在内部维护了阻塞队列,用以存储FutureTask的结果放入至队列中
  • 区别在于,submit方法是否需要一个集合来保存结果集,ExecutorService需要主动创建一个集合,
  • 而ExecutorCompletionService 内部集成了阻塞队列用以保存结果集

后续学到更多,再来补充


2020年3月20日14点45分补充
在疫情期间看完了《Java并发编程实战》,其中提到了ExecutorCompletionService这个对象。

举个例子

比如,页面上有5个不同的网站的广告位,然后我们需要将其渲染至前端页面上。

方案一 图片和文字同步加载

  1. loading html text …
  2. loading five images…
  3. show page
    效果怎么样?
    1、同步加载,导致页面刷新效率过低,用户体验差;
    2、如果从其他网站加载广告位的时候太慢,后台在同步阻塞,这个就很蛋疼了把。

方案二 图片异步加载

  1. loading html text …
  2. async loading five images…
  3. show page
    效果怎么样?
    1、页面正常文字可观,且立马加载,用户体验非常好。
    2、图片采用文本框预留,加载好了直接使用
    3、这个时候虽然是启用了5个线程加载的五个广告(假设分别为A,B,C,D,E),也就是产生了List<Future> ans对象,因为我们不清楚哪个网站的延迟最低,能让我们优先取得结果是不是?
    假设list中的数组就是按照[A,B,C,D,E]存储,那么我们在get的时候,阻塞取结果集的时候,第一个拿的就是A,这个时候A恰好是延迟最高的那个,这个时候B,C,D,E都已经返回了广告在Future中set了,这个时候只有等A完成后,才能迭代到B把,那你说,虽然能异步加载,但是感觉差了点意思,就不能那个广告好了,我页面就加载那个显示处理?我看到别人的页面都是这样的啊,是吧!

方案三 图片异步加载,采用ExecutorCompletionService

  1. loading html text …
  2. async loading five images…
  3. show page
    效果怎么样?
    1、页面正常文字可观,且立马加载,用户体验非常好。
    2、图片采用文本框预留,加载好了直接使用
    3、先说结果,采用ExecutorCompletionService的特点是,异步集合任务,谁先完成,谁先进入阻塞队列中,然后页面直接take()即可,因为take()只会在队列没有元素的时候,也就是没有图片的时候,才会阻塞,即使A延迟最高,但是我B,C,D,E只要其中一个完成了,那么就会立即add()至阻塞队列中,这样我页面获取的时候,就可以立马先加载一个图片了是不是,是不是?厉害把!
    这还不是最厉害的。假设我们页面有广告加载限制,如果指定3秒时间内,没有加载到图片,那么当前这个任务直接放弃,这个时候正好可以利用阻塞队列的poll(long timeout, TimeUnit unit)如果指定时间内没有获取到,那么我任性,本次不加载了,当然等待指定时间的wait操作FutureTask也是有的。

探寻ExecutorCompletionService源码。

public class ExecutorCompletionService<V> 
				implements CompletionService<V> {
    private final Executor executor;
    private final AbstractExecutorService aes;
    //阻塞队列,本质为链式阻塞队列
    private final BlockingQueue<Future<V>> completionQueue;
    // 关键点在这里,QueueingFuture 继承了FutureTask并且
    //重写了其钩子函数done(){} 然后将结果添加至阻塞队列中
    private static class QueueingFuture<V> extends FutureTask<Void> {
        QueueingFuture(RunnableFuture<V> task,
                       BlockingQueue<Future<V>> completionQueue) {
            super(task, null);
            this.task = task;
            this.completionQueue = completionQueue;
        }
        private final Future<V> task;
        private final BlockingQueue<Future<V>> completionQueue;
        //就是它
        protected void done() { completionQueue.add(task); }
    }
    //....其他源码
 }

该钩子函数的意义是当任务完成时,添加至阻塞队列。
done被调用不仅限于set任务完成,也包括cancel,和异常捕捉。

总结

ExecutorCompletionService 其重新了FutureTask的钩子方法done(),并且利用阻塞队列的特性完成任务与结果集的进一步解耦,达到更好的并行获取结果集效果。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值