CompletionService

本文介绍了如何利用Java的CompletionService实现一个高性能的询价应用,它能够从多个电商获取报价并保存到数据库。CompletionService通过内部的阻塞队列管理任务结果,使得先完成的报价能优先保存,简化了并发编程。示例代码展示了ExecutorCompletionService的使用,并提及了其在Dubbo Forking Cluster中的应用,强调了在批量异步任务管理中的优势。
摘要由CSDN通过智能技术生成

CompletionService

需求: 要做一个询价应用,这个应用需要从三个电商询价,然后保存在自己的数据库里

 @PostMapping("/account/testSynFirst")
    public void testSynFirst() throws InterruptedException {
        ExecutorService executor = Executors.newFixedThreadPool(3);//创建线程池
        Future<Integer> f1 = executor.submit(()->getPriceByS1()); //异步向S1询价
        Future<Integer> f2 = executor.submit(()->getPriceByS2()); //异步向S2询价
        Future<Integer> f3 = executor.submit(()->getPriceByS3()); //异步向S3询价
        BlockingQueue<Integer> bq = new LinkedBlockingDeque<>(); //创建阻塞队列
        executor.execute(()-> {
            try {
                bq.put(f1.get());   //电商S1报价异步进入阻塞队列
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (ExecutionException e) {
                e.printStackTrace();
            }
        });
        executor.execute(()-> { //电商S2报价异步进入阻塞队列
            try {
                bq.put(f2.get());
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (ExecutionException e) {
                e.printStackTrace();
            }
        });
        executor.execute(()-> { //电商S3报价异步进入阻塞队列
            try {
                bq.put(f3.get());
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (ExecutionException e) {
                e.printStackTrace();
            }
        });
        for (int i = 0; i < 3; i++) {
            Integer r = bq.take();
            executor.execute(()->save(r));
        }
    }

利用CompletionService实现询价系统
不过在实际项目中,并不建议你这样做,因为JAVA SDK并发包里已经提供了设计精良的CompletionService。利用CompletionService不但能帮你解决先获取到的报价先保存到数据库的问题,而且还能让代码更精炼

CompletionService的实现原来也是内部维护了一个阻塞队列,当任务执行结束就把任务的执行结果加入到阻塞队列中,不同的是CompletionService是把任务执行结果的Future对象加入到阻塞队列中,而上面的示例代码是把任务执行的最终结果放入到了阻塞队列中

CompletionService的使用
CompletionService接口的实现类是ExecutorCompletionService,这个实现类的构造方法有两个,分别是:

ExecutorCompletionService(Executor executor);

ExecutorCompletionService(Executor executor,BlockingQueue<Future<V>> completionQueue);

这两个构造方法都需要传入一个线程池,如果不指定completionQueue,那么默认会使用无界的LinkBlockingQueue,任务执行结果的Future对象就是加入到completionQueue中

下面的示例代码完整的展示了如何利用CompletionService来实现高性能的询价系统。其中,我们没有指定completionQueue,因此默认使用无界的LinkBlockingQueue

 @PostMapping("/account/testSynSecond")
    public void testSynSecond() throws InterruptedException, ExecutionException {
        ExecutorService executor = Executors.newFixedThreadPool(3);//创建线程池
        CompletionService<Integer> cs = new ExecutorCompletionService<>(executor); //创建CompletionService
        cs.submit(()->getPriceByS1());//异步向电商S1询价
        cs.submit(()->getPriceByS2());//异步向电商S2询价
        cs.submit(()->getPriceByS3());//异步向电商S3询价
        for (int i = 0; i < 3; i++) {
            Integer r = cs.take().get();
            executor.execute(()->save(r));
        }
    }

CompletionService接口说明
CompletionService接口提供的方法有5个,这5个方法的方法签名如下所示:
其中,submit()相关的方法有两个。一个方法参数是Callable task,另一个方法有两个参数,分别是Runnable task和 V result,这个方法类似于ThreadPoolExecutor的 Future submit(Runnable task, V result)。

CompletionService接口其他的3个方法,都是和阻塞队列相关的,take(),poll()都是从阻塞队列中获取并移除一个元素;它们的区别在于如果阻塞队列是空的,那么调用take()方法的线程会被阻塞,而poll()会返回null值。poll(long timeout, TimeUnit unit)方法支持以超时的方式获取并移除阻塞队列头部的一个元素,如果等待了TimeUnit unit时间,阻塞队列还是空的,那么该方法会返回null值。

 Future<V> submit(Callable<V> task);
 Future<V> submit(Runnable task, V result);
 Future<V> take() throws InterruptedException;
 Future<V> poll();
 Future<V> poll(long timeout, TimeUnit unit) throws InterruptedException;

利用CompletionService实现Dubbo中的Forking Cluster

Dubbo中有一种叫着Forking的集群模式,这种集群模式下,支持并行的调用多个查询服务,只要有一个成功返回结果,整个服务就可以返回了。

   @PostMapping("/account/testSynThird")
    public Integer testSynThird() throws InterruptedException, ExecutionException {
        ExecutorService executor = Executors.newFixedThreadPool(3);
        CompletionService<Integer> cs = new ExecutorCompletionService<>(executor);//创建CompletionService
        List<Future<Integer>> futures = new ArrayList<>(3);//用于保存Future对象
        //提交异步任务,并保存future到futures
        futures.add(cs.submit(()->getPriceByS1()));
        futures.add(cs.submit(()->getPriceByS2()));
        futures.add(cs.submit(()->getPriceByS3()));
        //获取最快返回的任务执行结果
        Integer r = 0;
        try {
            for (int i = 0; i < 3; i++) {
                r = cs.take().get();
                //简单的通过判空来检查是否成功返回
                if (r != null) {
                    break;
                }
            }
        } finally {
            //取消所有任务
            for (Future<Integer> f : futures) {
                f.cancel(true);
            }
        }
        System.out.println(r);
        return r;
    }

总结
当需要批量提交异步任务的时候建议你使用CompletionService。CompletionService将线程池Executor和阻塞队列BlockingQueue的功能联合到了一起,能够让批量异步任务的管理更简单。除此之外,CompletionService能够让异步任务的执行结果有序化,先执行完成的先进入阻塞队列,利用这个特性,你可以轻松实现后续处理的有序性,避免无谓的等待,同时还可以快速实现诸如Forking Cluster这样的需求

CompletionService的实现类ExecutorCompletionService,需要你自己创建线程池,随看上去有些啰嗦,但好处是你可以让多个ExecutorCompletionService的线程池隔离,这种隔离性能能避免几个特别耗时的任务拖垮整个应用的风险

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值