CompletionService 与 ExecutorService 之间的区别

在讨论二者之间的区别之前,先交待一下背景。

看了ElasticSearch Transport模块的源码,里面充满了各种异步回调获取结果,于是就想:为什么不用Callable接口,然后再基于java.util.concurrent.Future#get()获取任务的执行结果呢?

又因为ES的Transport模块底层是基于Netty实现的,研究了下Netty的获取线程执行结果的方式,,虽然Callable、FutureTask 将提交任务执行"异步化"了,但是在获取任务执行结果的这一步,JDK Future#get() 是阻塞的(超时阻塞),那么,能不能在获取结果的时候也不阻塞呢?有二种渠道实现:

  • 回调机制

    ElasticSearch里面就是大量用到回调机制。由于JDK Future的缺陷,Netty的 ChannelFuture扩展了JDK 的Future接口,并提供了回调机制支持异步获取任务的执行结果。它的源码:io.netty.channel.ChannelFuture的注释非常值得一读。

  • JDK8 里面提供的java.util.concurrent.CompletableFuture

    CompletableFuture 可参考《JAVA8实战》中了解一下

当然,本文不打算讨论,获取任务执行结果也不阻塞的具体实现方法,而是"先退一步",来分析下:

  • 使用 CompletionService 的 submit 方法 java.util.concurrent.CompletionService#submit(java.util.concurrent.Callable)提交多个任务,如何获取任务的执行结果?
  • 使用 ExecutorService 的submit 方法 java.util.concurrent.ExecutorService#submit(java.util.concurrent.Callable)提交多个任务,如何获取任务的执行结果?

为什么强调多个任务,因为这里讨论的是多个任务的并发执行。并不是第一个任务执行完成后,才能执行第二个任务。那CompletionService 与 ExecutorService 在获取任务结果的时候的区别是什么?

先说下结论,如果我们的目标是尽快处理任务的执行结果,而不是必须等到所有的任务都执行完成后,拿到所有的执行结果,才能进行下一步处理,那么使用 CompletionService 是非常有好处的。

举个例子:一个网站要显示10幅图像,下载完一幅就显示一幅,而不需要将这10幅都下载下来,再统一显示,那就很适合用CompletionService。下载 就是线程要执行的任务,图像 就是任务的执行结果。

使用ExecutorService时,代码是这样的:

//保存 Future<Image>,后面遍历 List 获取 Future 结果
List<Future<Image>> futureList = new ArrayList();
for(int i = 0; i < 10; i++)
{
Future<Image> imageFuture = executorService.submit(downloadTask);//10个下载任务同时并发
futureList.add(imageFuture);
} //获取10个任务的执行结果
for(int i = 0; i < 10; i++)
{
Future<Imapge> future = futureList.get(i);
Imapge image = future.get();//如果图像尚未下载完成,这里会阻塞
render(image);//将已经下载好的图像渲染到界面
}

我们是用List<Future<Image>>保存所有的任务Future,然后在for循环里面遍历List获取结果,假设第一个任务下载第一幅图像,第二个任务下载第2幅图像,以此类推....

这里的问题是:若第一幅图像未下载完成,但是第2幅、第3幅图像已经下载完了,我们也无法优先获取第2幅、第3幅图像。也就无法将已经先下载下来的图像渲染到界面。

总结起来讲就是:提交任务,将任务添加到List里面的顺序,与任务实际完成顺序是不相关的。

而使用 CompletionService,就能解决这个缺陷。它使得我们能够获得那些最先下载好的图像。

使用 CompletionService时,代码是这样的:

for(int i = 0; i < 10; i++)
{
completionService.submit(downloadTask);//10个下载任务同时并发
} for(int i = 0; i<10;i++ )
{
//只要任一幅图像下载下来了,completionService.take()就会返回,从而 get() 到这幅图像
render(completionService.take().get());
}

completionService.take()是个阻塞方法,如果10幅图像中都没下载下来,那就阻塞了。但只要有一幅下载下来了,就立即能获得到这幅图像。显然,这里:获取任务的执行结果的顺序与提交任务的顺序无关了。

这里的实现思路也可参考《Java并发编程实战》中第6章。

参考:

CompletionService 与 ExecutorService 获取任务执行结果时的区别的更多相关文章

  1. Java获取多线程执行结果方式的归纳与总结

    在日常的项目开发中,我们会经常遇到通过多线程执行程序并需要返回执行结果的场景,下面我们就对获取多线程返回结果的几种方式进行一下归纳,并进行简要的分析与总结. 一.Thread.join 在一些简单的应 ...

  2. CompletionService 和ExecutorService的区别和用法

    JavaSE5的Java.util.concurrent包中的执行器(Executor)将为你管理Thread对象,从而简化了并发编程.Executor在客户端和执行任务之间提供了一个间接层,Exec ...

  3. TaskTracker获取并执行map或reduce任务的过程1

    TaskTracker获取并执行map或reduce任务的过程(一) 我们知道TaskTracker在默认情况下,每个3秒就行JobTracker发送一个心跳包,也就是在这个心跳包中包含对任务的请求. ...

  4. 使用dynamic引发的异常:无法对 null 引用执行运行时绑定

    今天上午运营反映有商户的账单没有生成. 查看日志,在批量生成账单服务执行过程中,因为如下异常而中断了: 跑批异常 Microsoft.CSharp.RuntimeBinder.RuntimeBinde ...

  5. java 中多线程和锁的使用以及获取多线程执行结果

    多线程一:原生的写法   关键词 implements  实现  Runnable 类 run()  方法 注意点 : 创建类的实例 InterfaceController inter=new Int ...

  6. 获取 SharpSvn 执行 svn 操作的实时日志

    1 获取 SharpSvn 操作日志的方式 之前一篇随笔(使用 SharpSvn 执行 svn 操作)讲到可以通过声称一个绑定到一个 SvnClient 对象的 SvnClientReport 对象. ...

  7. Java: 获取当前执行位置的文件名/类名/方法名/行号

    在 JAVA 程序有时需要获取当前代码位置, 于是就利用 Thread.currentThread().getStackTrace() 写了下面这个工具类, 用来获取当前执行位置处代码的文件名/类名/ ...

  8. centos7.x 将普通用户加入sudoers中,获取sudo执行权限

    centos7.x 将普通用户加入sudoers中,获取sudo执行权限 0 问题由来   你在提起兴致学习的时候,兴冲冲地往黑洞洞的文本窗口输入下一行: [pan@localhost ~]$ sud ...

  9. java获取音频文件播放时长

    方法一: 在项目开发过程中,需要获取音视频文件时长.查询资料后发现 JAVE能够完美得到想要的结果,JAVE项目简介如下: The JAVE (Java Audio Video Encoder) li ...

随机推荐

  1. 批量保存云盘链接的demo

    写在前面的声明: 作为一个正在自学爬虫的小白,用爬虫爬了八千本书的云盘链接,然后就想把这写链接的资源都转存到自己的云盘里,以防某一天资源失效.本来想在网上找个能够批量保存的软件,哪知道找到几个都不能用 ...

  2. IIS发布网站Microsoft JET Database Engine 错误 '80004005'的解决办法,基于Access数据库

    在网站发布后,访问网站会有80004005的错误提示. 项目环境 项目基于Access数据库,server2012,文件系统为NTFS格式. 错误信息 Microsoft JETDatabase En ...

  3. Linux学习-账号管理

    新增与移除使用者: useradd, 相关配置文件, passwd, usermod, userdel 我们登入系统时会输入 (1)账号与 (2)密码,所以建立一个可用的账号同样的也需要这两个数据.那 ...

  4. dict 字典的常用操作

    #dict 字典的常用操作: id_db.get() #获取 id_db.update() #更新(覆盖)字典 id_db.values() #打印字典里所有的values id_db.keys() ...

  5. jeecg使用uploadify上传组件

    在jeecg框架的系统内使用uploadify组件进行上传操作,有时无法正常发送请求,一直被重定向到登录请求,有可能使系统对上传操作进行了过滤,需要将这个上传请求放到非拦截序列里,才能正常使用. 第二 ...

  6. python中json操作了解

    什么是接口? 交换数据 http://openweathermap.org/current json简介 JSON 是存储和交换文本信息的语法.类似 XML JSON 语法是 JavaScript 语 ...

  7. Android 程序 LinearLayout布局 参数layout_weight 探讨

    官方参考文档 对LinearLayout.LayoutParams中的android:layout_weight解释如下:Indicates how much of the extra space i ...

  8. [linx学习篇] ssh远程服务器免密码

    第一步:生成密钥.在终端下执行命令: ssh-keygen -t rsa 一路回车,各种提示按默认不要改,等待执行完毕.然后执行: ls ~/.ssh #可以看到两个密钥文件:id_rsa(私钥) i ...

  9. Problem 1004: 蛤玮打扫教室(区间覆盖端点记录)

    Problem 1004: 蛤玮打扫教室 Time Limits:  1000 MS   Memory Limits:  65536 KB 64-bit interger IO format:  %l ...

  10. BZOJ 4584 [Apio2016]赛艇 ——动态规划

    Subtask 1 直接$N^2$ $DP$,就可以了 Subtask 2 用$f[i][j]$表示当前位置为$i$,结束元素为$j$的方案数. Subtask 3 看下面 Subtask 4 首先可 ...