美文网首页
CompletionService

CompletionService

作者: woshishui1243 | 来源:发表于2019-12-01 23:15 被阅读0次

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

    // 创建线程池
    ExecutorService executor =
      Executors.newFixedThreadPool(3);
    // 异步向电商 S1 询价
    Future<Integer> f1 = 
      executor.submit(
        ()->getPriceByS1());
    // 异步向电商 S2 询价
    Future<Integer> f2 = 
      executor.submit(
        ()->getPriceByS2());
    // 异步向电商 S3 询价
    Future<Integer> f3 = 
      executor.submit(
        ()->getPriceByS3());
    // 创建阻塞队列
    BlockingQueue<Integer> bq =
      new LinkedBlockingQueue<>();
    // 电商 S1 报价异步进入阻塞队列  
    executor.execute(()->
      bq.put(f1.get()));
    // 电商 S2 报价异步进入阻塞队列  
    executor.execute(()->
      bq.put(f2.get()));
    // 电商 S3 报价异步进入阻塞队列  
    executor.execute(()->
      bq.put(f3.get()));
    // 异步保存所有报价  
    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,那么默认会使用无界的 LinkedBlockingQueue。任务执行结果的 Future 对象就是加入到 completionQueue 中。

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

    // 创建线程池
    ExecutorService executor = 
      Executors.newFixedThreadPool(3);
    // 创建 CompletionService
    CompletionService<Integer> cs = new 
      ExecutorCompletionService<>(executor);
    // 异步向电商 S1 询价
    cs.submit(()->getPriceByS1());
    // 异步向电商 S2 询价
    cs.submit(()->getPriceByS2());
    // 异步向电商 S3 询价
    cs.submit(()->getPriceByS3());
    // 将询价结果异步保存到数据库
    for (int i=0; i<3; i++) {
      Integer r = cs.take().get();
      executor.execute(()->save(r));
    }
    

    CompletionService 接口说明

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

    CompletionService 接口其余的 3 个方法,都是和阻塞队列相关的,take()、poll() 都是从阻塞队列中获取并移除一个元素;它们的区别在于如果阻塞队列是空的,那么调用 take() 方法的线程会被阻塞,而 poll() 方法会返回 null 值。 poll(long timeout, TimeUnit unit) 方法支持以超时的方式获取并移除阻塞队列头部的一个元素,如果等待了 timeout 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 的集群模式,这种集群模式下,支持并行地调用多个查询服务,只要有一个成功返回结果,整个服务就可以返回了。

    // 创建线程池
    ExecutorService executor =
      Executors.newFixedThreadPool(3);
    // 创建 CompletionService
    CompletionService<Integer> cs =
      new ExecutorCompletionService<>(executor);
    // 用于保存 Future 对象
    List<Future<Integer>> futures =
      new ArrayList<>(3);
    // 提交异步任务,并保存 future 到 futures 
    futures.add(
      cs.submit(()->geocoderByS1()));
    futures.add(
      cs.submit(()->geocoderByS2()));
    futures.add(
      cs.submit(()->geocoderByS3()));
    // 获取最快返回的任务执行结果
    Integer r = 0;
    try {
      // 只要有一个成功返回,则 break
      for (int i = 0; i < 3; ++i) {
        r = cs.take().get();
        // 简单地通过判空来检查是否成功返回
        if (r != null) {
          break;
        }
      }
    } finally {
      // 取消所有任务
      for(Future<Integer> f : futures)
        f.cancel(true);
    }
    // 返回结果
    return r;
    

    总结

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

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

    相关文章

      网友评论

          本文标题:CompletionService

          本文链接:https://www.haomeiwen.com/subject/ztitgctx.html