Java并发编程之CompletionService

来源:互联网 发布:淘宝转换率2.5正常么 编辑:程序博客网 时间:2024/05/17 22:20



当向Executor提交多个任务并且希望获得它们在完成之后的结果,如果用FutureTask,可以循环获取task,并调用get方法去获取task行结果,但是如果task还未完成,获取结果的线程将阻塞直到task完成,由于不知道哪个task优先执行完毕,使用这种方式效率不会很高。在jdk5时候提出接口CompletionService,它整合了Executor和BlockingQueue的功能,可以更加方便在多个任务执行时获取到任务执行结果。





  • 需求:不使用求和公式,计算从1到1000000相加的和。

  • 分析设计:需求指明不能使用求和公式,只能循环依次相加,为了提高效率,我们可以将1到1000000的数分为n段由n个task执行,执行结束后merge结果求最后的和。

  • 代码实现:

  1. package com.somnus.thread.pool;
  2.  
  3. import java.util.concurrent.Callable;
  4. import java.util.concurrent.CompletionService;
  5. import java.util.concurrent.ExecutionException;
  6. import java.util.concurrent.ExecutorCompletionService;
  7. import java.util.concurrent.ExecutorService;
  8. import java.util.concurrent.Executors;
  9.  
  10. public class ExecutorCompletion2 {
  11.  
  12. public static void main(String[] args) throws InterruptedException {
  13. ExecutorService executor = Executors.newFixedThreadPool(100);
  14.  
  15. // 构建完成服务
  16. CompletionService<Long> completionService = new ExecutorCompletionService<Long>(executor);
  17.  
  18. final int groupNum = 1000000/100;
  19.  
  20. for (int i = 1; i <= 100; i++){
  21. final int start = (i -1) * groupNum +1;
  22. final int end = i * groupNum;
  23. completionService.submit(new Callable<Long>() {
  24. public Long call() {
  25. Long sum = 0L;
  26. for (int j = start; j <= end; j++) {
  27. System.out.println(Thread.currentThread().getName() + " is looping of " + j + " for start of " + start + " for end of " + end);
  28. sum += j;
  29. }
  30. return sum;
  31. }
  32. });//返回结果类型FutureTask
  33. }
  34.  
  35. System.out.println("你走你的,我先走一步");
  36. Thread.sleep(10000);//模拟业务逻辑也在做自己的事情,同时开工
  37.  
  38. long result = 0L;
  39.  
  40. for (int i = 0; i < 100; i++) {
  41. try {
  42. result += completionService.take().get();
  43. } catch (InterruptedException e) {
  44. e.printStackTrace();
  45. } catch (ExecutionException e) {
  46. e.printStackTrace();
  47. }
  48. }
  49.  
  50. // 所有任务已经完成,关闭线程池
  51. System.out.println("执行完毕...." + result);
  52. executor.shutdown();
  53. }
  54.  
  55. }
  • 声明task执行载体,线程池executor
  • 声明CompletionService,通过参数指定执行task的线程池,存放已完成状态task的阻塞队列,队列默认为基于链表结构的阻塞队列LinkedBlockingQueue
  • 调用submit方法提交task
  • 调用take方法获取已完成状态task



CompletionService接口提供五个方法:

  • Future<V> submit(Callable<V> task) 
    提交Callable类型的task;
  • Future<V> submit(Runnable task, V result) 
    提交Runnable类型的task;
  • Future<V> take() throws InterruptedException 
    获取并移除已完成状态的task,如果目前不存在这样的task,则等待;
  • Future<V> poll() 
    获取并移除已完成状态的task,如果目前不存在这样的task,返回null;
  • Future<V> poll(long timeout, TimeUnit unit) throws InterruptedException 
    获取并移除已完成状态的task,如果在指定等待时间内不存在这样的task,返回null

接下来我们来看看CompletionService接口的具体实现:ExecutorCompletionService




  • 成员变量
                     

ExecutorCompletionService有三个成员变量: 
1. executor:执行task的线程池,创建CompletionService必须指定; 
2. aes:主要用于创建待执行task; 
3. completionQueue:存储已完成状态的task,默认是基于链表结构的阻塞队列LinkedBlockingQueue

  • 构造方法
                     

ExecutorCompletionService提供两个构造方法,具体的使用具体情况具体分析,使用者可以根据业务场景来进行选择。

  • task提交 
    ExecutorCompletionService提供submit方法来提交Callable类型或者Runnable类型的task

     

具体的执行流程如下:

  1. 参数校验,不符合条件的task抛出异常,程序结束;
  2. Callable类型或者Runnable类型的task构造成FutureTask
  3. 把构造好的FutureTask交由线程池executor执行。
看到这里可能大家会比较疑惑了,task调用submit方法可以提交,完成的task是什么时候被加入到completionQueue里的呢?针对这

个问题,从submit方法的源码可以看出,在提交到线程池的时候需要将FutureTask封装成QueueingFuture,我们来看

QueueingFuture的具体实现:

     


从源码可以看出,QueueingFutureFutureTask的子类,实现了done方法,在task执行完成之后将当前task添加到completionQueuedone方法的具体调用在FutureTaskfinishCompletion方法,上篇介绍FutureTask的文章已经做过具体的分析,在这里就不再赘述了。

  • 已完成状态task获取 
    CompletionServicetake方法和poll方法都可以获取已完成状态的task,我们来看看具体的实现:
     

从源码可以看出,takepoll都是调用BlockingQueue提供的方法。既然takepoll都可以获取到已完成状态的task,那么他们的区别是什么呢?

  • take在获取并移除已完成状态的task时,如果目前暂时不存在这样的task,等待,直到存在这样的task

  • poll在获取并移除已完成状态的task时,如果目前暂时不存在这样的task,不等待,直接返回null




阅读全文
0 0
原创粉丝点击