CompletionService简介、原理以及小案例
博客1:http://www.oschina.net/question/12_11255
博客2:
CompletionService简介
CompletionService与ExecutorService类似都可以用来执行线程池的任务,ExecutorService继承了Executor接口,而CompletionService则是一个接口,那么为什么CompletionService不直接继承Executor接口呢?主要是Executor的特性决定的,Executor框架不能完全保证任务执行的异步性,那就是如果需要实现任务(task)的异步性,只要为每个task创建一个线程就实现了任务的异步性。代码往往包含new Thread(task).start()。这种方式的问题在于,它没有限制可创建线程的数量(在ExecutorService可以限制),不过,这样最大的问题是在高并发的情况下,不断创建线程异步执行任务将会极大增大线程创建的开销、造成极大的资源消耗和影响系统的稳定性。另外,Executor框架还支持同步任务的执行,就是在execute方法中调用提交任务的run()方法就属于同步调用。
一般情况下,如果需要判断任务是否完成,思路是得到Future列表的每个Future,然后反复调用其get方法,并将timeout参数设为0,从而通过轮询的方式判断任务是否完成。为了更精确实现任务的异步执行以及更简便的完成任务的异步执行,可以使用CompletionService。
CompletionService实现原理
CompletionService实际上可以看做是Executor和BlockingQueue的结合体。CompletionService在接收到要执行的任务时,通过类似BlockingQueue的put和take获得任务执行的结果。CompletionService的一个实现是ExecutorCompletionService,ExecutorCompletionService把具体的计算任务交给Executor完成。
在实现上,ExecutorCompletionService在构造函数中会创建一个BlockingQueue(使用的基于链表的无界队列LinkedBlockingQueue),该BlockingQueue的作用是保存Executor执行的结果。当计算完成时,调用FutureTask的done方法。当提交一个任务到ExecutorCompletionService时,首先将任务包装成QueueingFuture,它是FutureTask的一个子类,然后改写FutureTask的done方法,之后把Executor执行的计算结果放入BlockingQueue中。QueueingFuture的源码如下:
private class QueueingFuture extends FutureTask<Void> {
QueueingFuture(RunnableFuture<V> task) {
super(task, null);
this.task = task;
}
protected void done() { completionQueue.add(task); }
private final Future<V> task;
}
从代码可以看到,CompletionService将提交的任务转化为QueueingFuture,并且覆盖了done方法,在done方法中就是将任务加入任务队列中。这点与之前对Executor框架的分析是一致的。
使用ExecutorService实现任务
代码模拟了电商中加载商品详情这一操作,因为商品属性的多样性,所以可以将商品的图片显示与商品简介的显示设为两个独立执行的任务。另外,由于商品的图片可能有许多张,所以图片的显示往往比简介显示更慢。这个时候异步执行能够在一定程度上加快执行的速度提高系统的性能。下面的代码演示了这点:
package com.rhwayfun.patchwork.concurrency.r0410; import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.concurrent.*; /**
* Created by rhwayfun on 16-4-10.
*/
public class DisplayProductInfoWithExecutorService { //线程池
private final ExecutorService executorService = Executors.newFixedThreadPool(2);
//日期格式器
private final DateFormat format = new SimpleDateFormat("HH:mm:ss"); // 模拟电商网站商品详情的信息展示
// 由于可能商品的图片可能会有很多张,所以显示商品的图片往往会有一定的延迟
// 除了商品的详情外还包括商品简介等信息的展示,由于这里信息主要的是文字为
// 主,所以能够比图片更快显示出来。下面的代码就以执行这两个任务为主线,完
// 成这两个任务的执行。由于这两个任务的执行存在较大差距,所以想到的第一个
// 思路就是异步执行,首先执行图像的下载任务,之后(不会很久)开始执行商品
// 简介信息的展示,如果网络足够好,图片又不是很大的情况下,可能在开始展示
// 商品的时候图像就下载完成了,所以自然想到使用Executor和Callable完成异
// 步任务的执行。 public void renderProductDetail() {
final List<ProductInfo> productInfos = loadProductImages(); //异步下载图像的任务
Callable<List<ProductImage>> task = new Callable<List<ProductImage>>() { @Override
public List<ProductImage> call() throws Exception {
List<ProductImage> imageList = new ArrayList<>();
for (ProductInfo info : productInfos){
imageList.add(info.getImage());
}
return imageList;
}
}; //提交给线程池执行
Future<List<ProductImage>> listFuture = executorService.submit(task);
//展示商品简介的信息
renderProductText(productInfos); try {
//显示商品的图片
List<ProductImage> imageList = listFuture.get();
renderProductImage(imageList);
} catch (InterruptedException e) {
// 如果显示图片发生中断异常则重新设置线程的中断状态
// 这样做可以让wait中的线程唤醒
Thread.currentThread().interrupt();
// 同时取消任务的执行,参数false表示在线程在执行不中断
listFuture.cancel(true);
} catch (ExecutionException e) {
try {
throw new Throwable(e.getCause());
} catch (Throwable throwable) {
throwable.printStackTrace();
}
} } private void renderProductImage(List<ProductImage> imageList ) {
for (ProductImage image : imageList){
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName() + " display products images! "
+ format.format(new Date()));
} private void renderProductText(List<ProductInfo> productInfos) {
for (ProductInfo info : productInfos){
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName() + " display products description! "
+ format.format(new Date()));
} private List<ProductInfo> loadProductImages() {
List<ProductInfo> list = new ArrayList<>();
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
ProductInfo info = new ProductInfo();
info.setImage(new ProductImage());
list.add(info);
System.out.println(Thread.currentThread().getName() + " load products info! "
+ format.format(new Date()));
return list;
} /**
* 商品
*/
private static class ProductInfo{
private ProductImage image; public ProductImage getImage() {
return image;
} public void setImage(ProductImage image) {
this.image = image;
}
} private static class ProductImage{} public static void main(String[] args){
DisplayProductInfoWithExecutorService cd = new DisplayProductInfoWithExecutorService();
cd.renderProductDetail();
System.exit(0);
}
}
代码的执行结果如下:

在上面的代码中,尝试并行执行商品图像的下载和简介信息的任务的执行,虽然这种方式能够完成任务,但是异构任务的并行对性能的提升还是有限的。考虑一种极端情况,商品图片的下载的速度远远小于简介信息的加载,那么这种情况(通常两者的加载速度的比例会是一个较大的值)下实际上任务的串行的执行效率就差不多了。而且使用了更复杂的代码,得到的提升却如此之小。只有当量相互独立并且同构的任务可以并发处理时,对系统性能的提升才是巨大的 (因为加载图片和简介执行速度相差太大,所以不是同构的任务)。
使用CompletionService实现任务
使用CompletionService的一大改进就是把多个图片的加载分发给多个工作单元进行处理,这样通过分发的方式就缩小了商品图片的加载与简介信息的加载的速度之间的差距,让这些小任务在线程池中执行,这样就大大降低了下载所有图片的时间,所以在这个时候可以认为这两个任务是同构的。使用CompletionService完成最合适不过了。
package com.rhwayfun.patchwork.concurrency.r0410; import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.concurrent.*; /**
* Created by rhwayfun on 16-4-10.
*/
public class DisplayProductInfoWithCompletionService { //线程池
private final ExecutorService executorService;
//日期格式器
private final DateFormat format = new SimpleDateFormat("HH:mm:ss"); public DisplayProductInfoWithCompletionService(ExecutorService executorService) {
this.executorService = executorService;
} public void renderProductDetail() { final List<ProductInfo> productInfos = loadProductInfos(); CompletionService<ProductImage> completionService = new ExecutorCompletionService<ProductImage>(executorService); //为每个图像的下载建立一个工作任务
for (final ProductInfo info : productInfos) {
completionService.submit(new Callable<ProductImage>() {
@Override
public ProductImage call() throws Exception {
return info.getImage();
}
});
} //展示商品简介的信息
renderProductText(productInfos); try {
//显示商品图片
for (int i = 0, n = productInfos.size(); i < n; i++){
Future<ProductImage> imageFuture = completionService.take();
ProductImage image = imageFuture.get();
renderProductImage(image);
}
} catch (InterruptedException e) {
// 如果显示图片发生中断异常则重新设置线程的中断状态
// 这样做可以让wait中的线程唤醒
Thread.currentThread().interrupt();
} catch (ExecutionException e) {
try {
throw new Throwable(e.getCause());
} catch (Throwable throwable) {
throwable.printStackTrace();
}
} } private void renderProductImage(ProductImage image) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " display products images! "
+ format.format(new Date()));
} private void renderProductText(List<ProductInfo> productInfos) {
for (ProductInfo info : productInfos) {
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName() + " display products description! "
+ format.format(new Date()));
} private List<ProductInfo> loadProductInfos() {
List<ProductInfo> list = new ArrayList<>();
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
ProductInfo info = new ProductInfo();
info.setImage(new ProductImage());
list.add(info);
System.out.println(Thread.currentThread().getName() + " load products info! "
+ format.format(new Date()));
return list;
} /**
* 商品
*/
private static class ProductInfo {
private ProductImage image; public ProductImage getImage() {
return image;
} public void setImage(ProductImage image) {
this.image = image;
}
} private static class ProductImage {
} public static void main(String[] args) {
DisplayProductInfoWithCompletionService cd = new DisplayProductInfoWithCompletionService(Executors.newCachedThreadPool());
cd.renderProductDetail();
}
}
执行结果与上面的一样。因为多个ExecutorCompletionService可以共享一个Executor,因此可以创建一个特定某个计算的私有的,又能共享公共的Executor的ExecutorCompletionService。
CompletionService小结
- 相比ExecutorService,CompletionService可以更精确和简便地完成异步任务的执行
- CompletionService的一个实现是ExecutorCompletionService,它是Executor和BlockingQueue功能的融合体,Executor完成计算任务,BlockingQueue负责保存异步任务的执行结果
- 在执行大量相互独立和同构的任务时,可以使用CompletionService
- CompletionService可以为任务的执行设置时限,主要是通过BlockingQueue的poll(long time,TimeUnit unit)为任务执行结果的取得限制时间,如果没有完成就取消任务
CompletionService简介、原理以及小案例的更多相关文章
- lvs负载简介,原理,常见使用案例及Keepalived高可用
Lvs简介 基础概念 LVS(Linux Virtual Server)即Linux虚拟服务器,是由张文嵩博士主导的开源负载均衡项目,目前LVS已经被集成到Linux内核模块中(2.6及以上版本内核) ...
- RxJava RxPermissions 动态权限 简介 原理 案例 MD
Markdown版本笔记 我的GitHub首页 我的博客 我的微信 我的邮箱 MyAndroidBlogs baiqiantao baiqiantao bqt20094 baiqiantao@sina ...
- Session与Cookie的原理以及使用小案例>从零开始学JAVA系列
目录 Session与Cookie的原理以及使用小案例 Cookie和Session所解决的问题 Session与Cookie的原理 Cookie的原理 Cookie的失效时机 小提示 Session ...
- ch1-vuejs基础入门(hw v-bind v-if v-for v-on v-model 应用组件简介 小案例)
1 hello world 引入vue.min.js 代码: ----2.0+版本 <div id="test"> {{str}} </div> <s ...
- JSONP跨域访问百度实现搜索提示小案例
一.JSONP简介 JSONP 全称 JSON with padding(填充式 JSON 或参数式 JSON),JSONP实现跨域请求的原理,就是动态创建<script>标签,然后利用& ...
- [jQuery学习系列六]6-jQuery实际操作小案例
前言最后在这里po上jQuery的几个小案例. Jquery例子1_占位符使用需求: 点击第一个按钮后 自动去check 后面是否有按钮没有选中, 如有则提示错误消息. <html> &l ...
- SqlDependency缓存数据库表小案例
SqlDependency的简介: SqlDependency是outputcache网页缓存的一个参数,它的作用是指定缓存失效的数据库依赖项,可以具体到数据库和表. SqlDependency能解决 ...
- javascript之JSON小案例,实现添加数据与清楚数据
对json应用给出一个小案例,加深一些理解: <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" & ...
- Android JSON解析库Gson和Fast-json的使用对比和图书列表小案例
Android JSON解析库Gson和Fast-json的使用对比和图书列表小案例 继上篇json解析,我用了原生的json解析,但是在有些情况下我们不得不承认,一些优秀的json解析框架确实十分的 ...
随机推荐
- [刘阳Java]_CSS鼠标悬停
小白在学习前端技术时候,一定会在刚开始的时候学习CSS2和CSS3的相关知识.这篇内容给大家介绍一个CSS鼠标悬停的效果.大家可以先看下面的效果图,然后我们在说一下实现的效果要求吧 上图效果非常简单和 ...
- js树形数据结构的扁平化
前面我们封装了一维数组(具备树形结构相关属性)处理成树形结构的方法:https://www.cnblogs.com/coder--wang/p/15013664.html 接下来我们来一波反向操作,封 ...
- js浮点数保留位数方法封装
大家在平时业务中应该经常跟小数打交道吧,有没有被小数点的保留位数问题搞得头疼啊.比如,保留一位小数,保留俩位小数,保留三位小数,向上取整.四舍五入等等. 而我最近在项目中正好遇到类似的问题:有的地方要 ...
- 如何热更新长缓存的 HTTP 资源
前言 HTTP 缓存时间一直让开发者头疼.时间太短,性能不够好:时间太长,更新不及时.当遇到严重问题需紧急修复时,尽管后端文件可快速替换,但前端文件仍从本地缓存加载,导致更新长时间无法生效. 对于这个 ...
- groff编写man页
groff 是大多数 Unix 系统上所提供的流行的文本格式化工具 nroff/troff 的 GNU 版本.它一般用于编写手册页,即命令.编程接口等的在线文档.在本文中,我们将给你展示如何使用 gr ...
- Requests方法 -- 参数化
import requests#禁用安全请求警告from requests.packages.urllib3.exceptions import InsecureRequestWarningreque ...
- centos7 源码安装指定版本的php7
很多时候可能会遇到需要手动源码安装软件的时候,所以自己实践了一把,并且把安装过程中遇到的问题,以及在网上找到的解决办法(实测有效)都记录下来,方便日后学习实践. 1. 系统环境 # cat /etc/ ...
- CSP-S 2020
游记 Day# 游记个鬼啊就在自家学校=-= 早上宿舍待不了,去机房颓废,看了几集猫和老鼠,并且把看门狗军团的流程看完了(真棒),甚至在考试之前把老师给的巧克力也吃完了. 期间zyt学长来摸鱼.他们今 ...
- Mysql命令语句
常用的管理命令 SHOW DATABASES; //显示当前服务器下所有的数据库 USE 数据库名称; //进入指定的数据 show tables; ...
- IDEA输出乱码“淇℃伅”
尝试过在IDEA中tomcat的vmoptions中加入代码 -Dfile.encoding=GBK 没什么用 最后找到了这个方法 参考链接 ①在Tomcat文件夹下找到conf ②找到logging ...