【java并发工具-分工】CompletionService:批量执行异步任务

CompletionService

    • 前言
    • 1.CompletionService原理:
    • 2.如何创建CompletionService?
    • 3.使用CompletionService完成询价系统的优化。
    • 4.CompletionService接口介绍
    • 5.利用CompletionService实现dubbo中的Forking Cluster

前言

怎么个批量获取异步任务?
看下面询价系统的实例代码:
如果异步查询电商S2的时间非常短,因为get()方法是阻塞方法,所以必须要等待f1.get()获取成功之后才可以向下执行。

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());    
// 获取电商S1报价并保存
r=f1.get();
executor.execute(()->save(r));  
// 获取电商S2报价并保存
r=f2.get();
executor.execute(()->save(r));  
// 获取电商S3报价并保存  
r=f3.get();
executor.execute(()->save(r));

那么怎么才可以获得先执行结束的任务结果呢?

  1. 你可能会想到阻塞队列,把get()获取结果异步执行,并存放到阻塞队列中,这样每次从阻塞队列中取出先执行结束的线程结果。
  2. JAVA SDK并发包中提供了的CompletionService就可以完成上面的操作,下面就来介绍CompletionService工具。

1.CompletionService原理:

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

2.如何创建CompletionService?

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

1.  ExecutorCompletionService(Executor executor)
2.  ExecutorCompletionService(Executor executor, BlockingQueue<Future<v>> completionQueue)

1.需要传入线程池。
2.除了需要传入一个线程池,还需要传入一个阻塞队列,任务的执行结果就是放在completionQueue队列中的。
如果不指定阻塞队列,默认使用无界的LinkedBlockQueue,任务执行结果的Future就是放在这里面的。

3.使用CompletionService完成询价系统的优化。

// 创建线程池
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();   //获取cs阻塞队列中的Future对象,然后获得结果int。
  executor.execute(()->save(r));
}

4.CompletionService接口介绍

CompletionService接口提供5个方法:

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;

两个submit提交方法。

  1. 一个参数是Callable,前面实例代码中提交任务就是用的它;
  2. 另外一个参数是Runnalbe,result,和Future一文中的其中一种submit方法类似。

剩下的三个方法都与阻塞队列有关。take(),poll()方法都是从阻塞队列中获取并移除一个元素.

  1. take():如果阻塞队列是空,调用take方法的线程会陷入阻塞。
  2. poll(): 如果阻塞队列是空,调用poll方法会返回null值,
  3. 并且poll()方法还支持超时等待的方式,如果等待超时后,队列还是空,就还是返回null值。

5.利用CompletionService实现dubbo中的Forking Cluster

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

例如你需要一个地图的服务,为了保证高可用和高性能,你可以并行调用三个地图服务商的API,然后只要有一个服务返回,这个服务就可以返回了。

下面看代码实例:

geocoder(addr) {
  //并行执行以下3个查询服务, 
  r1=geocoderByS1(addr);
  r2=geocoderByS2(addr);
  r3=geocoderByS3(addr);
  //只要r1,r2,r3有一个返回
  //则返回
  return r1|r2|r3;
}

利用CompletionService实现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()));//添加 异步任务返回的Future结果
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;

更多:邓新

你可能感兴趣的:(并发编程体系架构,#,java并发工具类)