CompletionService 与 ExecutorService 获取任务执行结果时的区别

CompletionService 与 ExecutorService 之间的区别

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

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

又由于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实战》中了解一下.net

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

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

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

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

举个例子:一个网站要显示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章。

原文:http://www.javashuo.com/article/p-pixotixi-x.html

相关文章
相关标签/搜索