博客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小结

  1. 相比ExecutorService,CompletionService可以更精确和简便地完成异步任务的执行
  2. CompletionService的一个实现是ExecutorCompletionService,它是Executor和BlockingQueue功能的融合体,Executor完成计算任务,BlockingQueue负责保存异步任务的执行结果
  3. 在执行大量相互独立和同构的任务时,可以使用CompletionService
  4. CompletionService可以为任务的执行设置时限,主要是通过BlockingQueue的poll(long time,TimeUnit unit)为任务执行结果的取得限制时间,如果没有完成就取消任务

 

CompletionService简介、原理以及小案例的更多相关文章

  1. lvs负载简介,原理,常见使用案例及Keepalived高可用

    Lvs简介 基础概念 LVS(Linux Virtual Server)即Linux虚拟服务器,是由张文嵩博士主导的开源负载均衡项目,目前LVS已经被集成到Linux内核模块中(2.6及以上版本内核) ...

  2. RxJava RxPermissions 动态权限 简介 原理 案例 MD

    Markdown版本笔记 我的GitHub首页 我的博客 我的微信 我的邮箱 MyAndroidBlogs baiqiantao baiqiantao bqt20094 baiqiantao@sina ...

  3. Session与Cookie的原理以及使用小案例>从零开始学JAVA系列

    目录 Session与Cookie的原理以及使用小案例 Cookie和Session所解决的问题 Session与Cookie的原理 Cookie的原理 Cookie的失效时机 小提示 Session ...

  4. 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 ...

  5. JSONP跨域访问百度实现搜索提示小案例

    一.JSONP简介 JSONP 全称 JSON with padding(填充式 JSON 或参数式 JSON),JSONP实现跨域请求的原理,就是动态创建<script>标签,然后利用& ...

  6. [jQuery学习系列六]6-jQuery实际操作小案例

    前言最后在这里po上jQuery的几个小案例. Jquery例子1_占位符使用需求: 点击第一个按钮后 自动去check 后面是否有按钮没有选中, 如有则提示错误消息. <html> &l ...

  7. SqlDependency缓存数据库表小案例

    SqlDependency的简介: SqlDependency是outputcache网页缓存的一个参数,它的作用是指定缓存失效的数据库依赖项,可以具体到数据库和表. SqlDependency能解决 ...

  8. javascript之JSON小案例,实现添加数据与清楚数据

    对json应用给出一个小案例,加深一些理解: <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" & ...

  9. Android JSON解析库Gson和Fast-json的使用对比和图书列表小案例

    Android JSON解析库Gson和Fast-json的使用对比和图书列表小案例 继上篇json解析,我用了原生的json解析,但是在有些情况下我们不得不承认,一些优秀的json解析框架确实十分的 ...

随机推荐

  1. PAT乙级:1088 三人行 (20分)

    PAT乙级:1088 三人行 (20分) 题干 子曰:"三人行,必有我师焉.择其善者而从之,其不善者而改之." 本题给定甲.乙.丙三个人的能力值关系为:甲的能力值确定是 2 位正整 ...

  2. sql语句优化(持续更新)

    1.对查询进行优化,应尽量避免全表扫描,首先应考虑在 where 及 order by 涉及的列上建立索引.2.应尽量避免在 where 子句中使用!=或<>操作符,否则将引擎放弃使用索引 ...

  3. 基于单机redis的分布式锁实现

    最近我们有个服务经常出现存储的数据出现重复,首先上一个系统流程图: 用户通过http请求可以通知任务中心结束掉自己发送的任务,这时候任务中心会通过MQ通知结束服务去结束任务保存数据,由于任务结束数据计 ...

  4. JAVA,IDEA下的包机制

    IDEA下的包机制 在实际开发中,大型系统往往需要多人合作完成,每个程序员都要命名多个类与接口等类型,难免会定义重复的类与接口.为了避免名字冲突,java提供了包机制只要包名不同,即使类名相同,也能相 ...

  5. Apereo CAS 4.1 反序列化命令执行漏洞

    命令执行 java -jar apereo-cas-attack-1.0-SNAPSHOT-all.jar CommonsCollections4 "touch /tmp/success&q ...

  6. 🔥 LeetCode 热题 HOT 100(11-20)

    20. 有效的括号 class Solution { public boolean isValid(String s) { Map<Character, Character> map = ...

  7. OpenFaaS实战之四:模板操作(template)

    欢迎访问我的GitHub https://github.com/zq2599/blog_demos 内容:所有原创文章分类汇总及配套源码,涉及Java.Docker.Kubernetes.DevOPS ...

  8. [源码解析]机器学习参数服务器ps-lite(4) ----- 应用节点实现

    [源码解析]机器学习参数服务器ps-lite(4) ----- 应用节点实现 目录 [源码解析]机器学习参数服务器ps-lite(4) ----- 应用节点实现 0x00 摘要 0x01 基础类 1. ...

  9. java中 字符串的构造方法和直接创建

    java.long.String类代表字符串.Java 程序中的所有字符串字面值(如 "abc" )都作为此类的实例实现.(程序当中所有的双引号字符串,都是String类的对象[没 ...

  10. VS2019 +MySQL+EntityFramework 使用配置与坑点避免随记

    一.安装运行环境 首先我们到mysql的官方网站上下载 mysql-installer-community-8.0.26.0 ,或者其他特定版本,通过它我们可以先将 mysql-for-visuals ...