1. public interface CompletionService<V> {
  2.  
  3. Future<V> submit(Callable<V> task);
  4.  
  5. Future<V> submit(Runnable task, V result);
  6.  
  7. /**
  8.  
  9. * Retrieves and removes the Future representing the next
  10.  
  11. * completed task, waiting if none are yet present.
  12.  
  13. *
  14.  
  15. * @return the Future representing the next completed task
  16.  
  17. * @throws InterruptedException if interrupted while waiting
  18.  
  19. * 阻塞/
  20.  
  21. Future<V> take() throws InterruptedException;
  22.  
  23. /**
  24.  
  25. * Retrieves and removes the Future representing the next
  26.  
  27. * completed task or <tt>null</tt> if none are present.
  28.  
  29. *
  30.  
  31. * @return the Future representing the next completed task, or
  32.  
  33. * <tt>null</tt> if none are present
  34.  
  35. * 非阻塞/
  36.  
  37. Future<V> poll();
  38.  
  39. }

CompletionService 也不是到处都能用,它不适合处理任务数量有限但个数不可知的场景。例如,要统计某个文件夹中的文件个数,在遍历子文件夹的时候也会“递归地”提交新的任务,但最后到底提交了多少,以及在什么时候提交完了所有任务,都是未知数,无论 CompletionService 还是线程池都无法进行判断。这种情况只能直接用线程池来处理。

CompletionService 接口的实例可以充当生产者和消费者的中间处理引擎,从而达到将提交任务和处理结果的代码进行解耦的目的。生产者调用submit 方法提交任务,而消费者调用 poll(非阻塞)或 take(阻塞)方法获取下一个结果:这一特征看起来和阻塞队列(BlockingQueue)类似,两者的区别在于 CompletionService 要负责任务的处理,而阻塞队列则不会。

在 JDK 中,该接口只有一个实现类 ExecutorCompletionService,该类使用创建时提供的 Executor 对象(通常是线程池)来执行任务,然后将结果放入一个阻塞队列中。

1. 背景

在Java5的多线程中,可以使用Callable接口来实现具有返回值的线程。使用线程池的submit方法提交Callable任务,利用submit方法返回的Future存根,调用此存根的get方法来获取整个线程池中所有任务的运行结果。

方法一:如果是自己写代码,应该是自己维护一个Collection保存submit方法返回的Future存根,然后在主线程中遍历这个Collection并调用Future存根的get()方法取到线程的返回值。

方法二:使用CompletionService类,它整合了Executor和BlockingQueue的功能。你可以将Callable任务提交给它去执行,然后使用类似于队列中的take方法获取线程的返回值。

2. 实现代码

  1. package com.clzhang.sample.thread;
  2.  
  3. import java.util.*;
  4.  
  5. import java.util.concurrent.BlockingQueue;
  6.  
  7. import java.util.concurrent.Callable;
  8.  
  9. import java.util.concurrent.CompletionService;
  10.  
  11. import java.util.concurrent.ExecutorCompletionService;
  12.  
  13. import java.util.concurrent.ExecutorService;
  14.  
  15. import java.util.concurrent.Executors;
  16.  
  17. import java.util.concurrent.Future;
  18.  
  19. import java.util.concurrent.LinkedBlockingQueue;
  20.  
  21. public class ThreadPoolTest4 {
  22.  
  23. // 具有返回值的测试线程
  24.  
  25. class MyThread implements Callable<String> {
  26.  
  27. private String name;
  28.  
  29. public MyThread(String name) {
  30.  
  31. this.name = name;
  32.  
  33. }
  34.  
  35. @Override
  36.  
  37. public String call() {
  38.  
  39. int sleepTime = new Random().nextInt(1000);
  40.  
  41. try {
  42.  
  43. Thread.sleep(sleepTime);
  44.  
  45. } catch (InterruptedException e) {
  46.  
  47. e.printStackTrace();
  48.  
  49. }
  50.  
  51. // 返回给调用者的值
  52.  
  53. String str = name + " sleep time:" + sleepTime;
  54.  
  55. System.out.println(name + " finished...");
  56.  
  57. return str;
  58.  
  59. }
  60.  
  61. }
  62.  
  63. private final int POOL_SIZE = 5;
  64.  
  65. private final int TOTAL_TASK = 20;
  66.  
  67. // 方法一,自己写集合来实现获取线程池中任务的返回结果
  68.  
  69. public void testByQueue() throws Exception {
  70.  
  71. // 创建线程池
  72.  
  73. ExecutorService pool = Executors.newFixedThreadPool(POOL_SIZE);
  74.  
  75. BlockingQueue<Future<String>> queue = new LinkedBlockingQueue<Future<String>>();
  76.  
  77. // 向里面扔任务
  78.  
  79. for (int i = 0; i < TOTAL_TASK; i++) {
  80.  
  81. Future<String> future = pool.submit(new MyThread("Thread" + i));
  82.  
  83. queue.add(future);
  84.  
  85. }
  86.  
  87. // 检查线程池任务执行结果
  88.  
  89. for (int i = 0; i < TOTAL_TASK; i++) {
  90.  
  91. System.out.println("method1:" + queue.take().get());
  92.  
  93. }
  94.  
  95. // 关闭线程池
  96.  
  97. pool.shutdown();
  98.  
  99. }
  100.  
  101. // 方法二,通过CompletionService来实现获取线程池中任务的返回结果
  102.  
  103. public void testByCompetion() throws Exception {
  104.  
  105. // 创建线程池
  106.  
  107. ExecutorService pool = Executors.newFixedThreadPool(POOL_SIZE);
  108.  
  109. CompletionService<String> cService = new ExecutorCompletionService<String>(pool);
  110.  
  111. // 向里面扔任务
  112.  
  113. for (int i = 0; i < TOTAL_TASK; i++) {
  114.  
  115. cService.submit(new MyThread("Thread" + i));
  116.  
  117. }
  118. // 检查线程池任务执行结果
  119.  
  120. for (int i = 0; i < TOTAL_TASK; i++) {
  121.  
  122. Future<String> future = cService.take();
  123.  
  124. System.out.println("method2:" + future.get());
  125.  
  126. }
  127.  
  128. // 关闭线程池
  129.  
  130. pool.shutdown();
  131.  
  132. }
  133.  
  134. public static void main(String[] args) throws Exception {
  135.  
  136. ThreadPoolTest4 t = new ThreadPoolTest4();
  137.  
  138. t.testByQueue();
  139.  
  140. t.testByCompetion();
  141.  
  142. }
  143.  
  144. }

部分输出:

  1. ...
  2.  
  3. Thread4 finished...
  4.  
  5. method1:Thread4 sleep time:833
  6.  
  7. method1:Thread5 sleep time:158
  8.  
  9. Thread6 finished...
  10.  
  11. method1:Thread6 sleep time:826
  12.  
  13. method1:Thread7 sleep time:185
  14.  
  15. Thread9 finished...
  16.  
  17. Thread8 finished...
  18.  
  19. method1:Thread8 sleep time:929
  20.  
  21. method1:Thread9 sleep time:575
  22.  
  23. ...
  24.  
  25. Thread11 finished...
  26.  
  27. method2:Thread11 sleep time:952
  28.  
  29. Thread18 finished...
  30.  
  31. method2:Thread18 sleep time:793
  32.  
  33. Thread19 finished...
  34.  
  35. method2:Thread19 sleep time:763
  36.  
  37. Thread16 finished...
  38.  
  39. method2:Thread16 sleep time:990
  40.  
  41. ...

3. 总结

使用方法一,自己创建一个集合来保存Future存根并循环调用其返回结果的时候,主线程并不能保证首先获得的是最先完成任务的线程返回值。它只是按加入线程池的顺序返回。因为take方法是阻塞方法,后面的任务完成了,前面的任务却没有完成,主程序就那样等待在那儿,只到前面的完成了,它才知道原来后面的也完成了。使用方法二,使用CompletionService来维护处理线程的返回结果时,主线程总是能够拿到最先完成的任务的返回值,而不管它们加入线程池的顺序。

CompletionService详解的更多相关文章

  1. Java并发之CompletionService详解

    CompletionService是什么? 它是JUC包中的一个接口类,默认实现类只有一个ExecutorCompletionService. CompletionService干什么的? 它将异步任 ...

  2. 跟着阿里p7一起学java高并发 - 第19天:JUC中的Executor框架详解1,全面掌握java并发核心技术

    这是java高并发系列第19篇文章. 本文主要内容 介绍Executor框架相关内容 介绍Executor 介绍ExecutorService 介绍线程池ThreadPoolExecutor及案例 介 ...

  3. java高并发系列 - 第20天:JUC中的Executor框架详解2之ExecutorCompletionService

    这是java高并发系列第20篇文章. 本文内容 ExecutorCompletionService出现的背景 介绍CompletionService接口及常用的方法 介绍ExecutorComplet ...

  4. Linq之旅:Linq入门详解(Linq to Objects)

    示例代码下载:Linq之旅:Linq入门详解(Linq to Objects) 本博文详细介绍 .NET 3.5 中引入的重要功能:Language Integrated Query(LINQ,语言集 ...

  5. 架构设计:远程调用服务架构设计及zookeeper技术详解(下篇)

    一.下篇开头的废话 终于开写下篇了,这也是我写远程调用框架的第三篇文章,前两篇都被博客园作为[编辑推荐]的文章,很兴奋哦,嘿嘿~~~~,本人是个很臭美的人,一定得要截图为证: 今天是2014年的第一天 ...

  6. EntityFramework Core 1.1 Add、Attach、Update、Remove方法如何高效使用详解

    前言 我比较喜欢安静,大概和我喜欢研究和琢磨技术原因相关吧,刚好到了元旦节,这几天可以好好学习下EF Core,同时在项目当中用到EF Core,借此机会给予比较深入的理解,这里我们只讲解和EF 6. ...

  7. Java 字符串格式化详解

    Java 字符串格式化详解 版权声明:本文为博主原创文章,未经博主允许不得转载. 微博:厉圣杰 文中如有纰漏,欢迎大家留言指出. 在 Java 的 String 类中,可以使用 format() 方法 ...

  8. Android Notification 详解(一)——基本操作

    Android Notification 详解(一)--基本操作 版权声明:本文为博主原创文章,未经博主允许不得转载. 微博:厉圣杰 源码:AndroidDemo/Notification 文中如有纰 ...

  9. Android Notification 详解——基本操作

    Android Notification 详解 版权声明:本文为博主原创文章,未经博主允许不得转载. 前几天项目中有用到 Android 通知相关的内容,索性把 Android Notificatio ...

随机推荐

  1. 我和domino不得不说的故事(连载2016-3-2)

    1.关于NotesViewEntry 注意:通过NotesViewEntry获取某列的值时,若该列的值为@IsExpandable or @DocNumber 或者是常量时,将不会显示. Set en ...

  2. win10/server2019 系统安装 详解

    https://www.microsoft.com/zh-cn/software-download/windows10 https://go.microsoft.com/fwlink/?LinkId= ...

  3. 下拉框改变事件:获取下拉框中当前选择的文本 SelectionChanged事件

    /// <summary> /// 下拉框改变事件:获取下拉框中当前选择的文本 /// </summary> /// <param name="sender&q ...

  4. Solaris11修改主机名

    在Solaris10中,主机名的修改是通过修改相关的配置文件实现的.在Solaris11中,主机名的配置信息已经转移到SMF配置库中,因此修改主机名的方式与Solaris10完全不同.以下是修改Sol ...

  5. oracle connect by用法篇 (包括树遍历)之二

    3.2查询当前时间往前的12周的开始时间.结束时间.第多少周 , ) - (rownum ) as startDate, sysdate , 'd'))) - (rownum ) as endDate ...

  6. 使用SharedPreferences接口来实现记住密码功能

    SharedPreferences接口非常适合用来存储零散的数据.这里我们用来实现记录用户名和密码的功能.在前面我用过IO流来实现记住密码的功能.那么用SharedPreferences接口会比用IO ...

  7. mysql in 方法查询 按照 in队列里的顺序排序

    String sql ' GROUP BY comm " + "order by field(comm,?,?,?,?,?,?,?,?)"; stmt = conn.pr ...

  8. 463. Island Perimeter岛屿周长

    [抄题]: You are given a map in form of a two-dimensional integer grid where 1 represents land and 0 re ...

  9. 为什么不推荐用破解版的winrar

    站在winrar公司的角度,作为winrar的开发商或者运营商当然不希望用户使用破解版的winrar,因为这损害了他们的利益,这是屁股问题. 站在用户的角度,我希望免费使用世界上所有的软件.但这个世界 ...

  10. Win32编程中如何处理控制台消息

    这篇文章讨论如何处理所有的控制台消息. 第一步,首先要安装一个事件钩子,也就是说要建立一个回调函数.调用Win32 API,原型如下: BOOL SetConsoleCtrlHandler(PHAND ...