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解析框架确实十分的 ...
随机推荐
- 答读者问(1):非模式物种找marker;如何根据marker定义细胞类型
下午花了两个小时回答读者的疑问,觉得可以记录下来,也许能帮到一部分人. 第一位读者做的是非模式物种的单细胞. 一开始以为是想问我非模式物种的marker基因在哪儿找,读者朋友也提到了blast 研究的 ...
- 前端开发入门到进阶第四集【使用sublime安装jshint和cssLint】
参考:https://blog.csdn.net/qq_27965129/article/details/52786224 使用sublime安装JSHint插件: 1,解决不能使用package c ...
- Linux上常用插件的一些命令
Linux上关于jdk tomcat MySQL dubbo等的一些启动,结束,查看状态的命名. 1.tomcat 运行tomcat cd bin/ 进入tomcat 的bin 文件夹,直接运行: . ...
- 跨域jsonp+jQuery+json+html动态生成表格
1.什么是跨域 浏览器对于javascript的同源策略的限制,例如a.cn下面的js不能调用b.cn中的js,对象或数据(因为a.cn和b.cn是不同域),所以跨域就出现了. 凡是拥有scr这个属性 ...
- kali操作系统安装google浏览器
安装的kali操作系统版本是kali-linux-2020.2-installer-amd64.iso 参考链接:https://www.cnblogs.com/Young-wind/p/585502 ...
- URL 参数为sql 有空格 的解决办法
var strsql=" select e.* from es_doc_main e where 1=1" +" and e.doccode='"+prtNo+ ...
- phpunit 远程代码执行漏洞(CVE-2017-9841)
影响版本 4.8.19 ~ 4.8.27 5.0.10 ~ 5.6.2 访问进行抓包 http://192.168.49.2:8080/vendor/phpunit/phpunit/src/Util/ ...
- Jenkins远程命令执行漏洞(CVE-2018-1000861)
此漏洞没有回显,直接利用orange的exp执行命令反弹shell 工具地址https://github.com/orangetw/awesome-jenkins-rce-2019 web服务器下写1 ...
- Apache ActiveMQ(CVE-2016-3088)
影响版本 Apache ActiveMQ 5.0.0-5.13.x 路径地址 http://192.168.49.2:8161/admin/test/systemProperties.jsp 该漏洞允 ...
- raven靶机
仅供个人娱乐 靶机信息 Raven 下载地址:https://www.vulnhub.com/entry/raven-1,256/ 一.主机探测 端口信息 目录扫描 80端口 根据页面开始搜寻有用的信 ...