以下是jdk关于CompletionService的简介:

public interface CompletionService<V>

  • 将生产新的异步任务与使用已完成任务的结果分离开来的服务。生产者submit 执行的任务。使用者take已完成的任务,并按照完成这些任务的顺序处理它们的结果。例如,CompletionService 可以用来管理异步 IO ,执行读操作的任务作为程序或系统的一部分提交,然后,当完成读操作时,会在程序的不同部分执行其他操作,执行操作的顺序可能与所请求的顺序不同。
  • 通常,CompletionService 依赖于一个单独的 Executor 来实际执行任务,在这种情况下,CompletionService 只管理一个内部完成队列。ExecutorCompletionService 类提供了此方法的一个实现。
  • 内存一致性效果:线程中向 CompletionService 提交任务之前的操作 happen-before 该任务执行的操作,后者依次 happen-before 紧跟在从对应 take() 成功返回的操作。
  • CompletionService采取的是BlockingQueue<Future<V>>无界队列来管理Future。则有一个线程执行完毕把返回结果放到BlockingQueue<Future<V>>里面。就可以通过 completionServcie.take().get()取出结果。
  • 方法区别:

  • take 方获取并移除表示下一个已完成任务的 Future,如果目前不存在这样的任务,则等待。<如果需要用到返回值建议用take>
  • poll 获取并移除表示下一个已完成任务的 Future,如果不存在这样的任务,则返回null。
public class ReportGenerator implements Callable<String> {
private String sender;
private String title;
public ReportGenerator(String sender, String title) {
this.sender = sender;
this.title = title;
}
@Override
public String call() throws Exception {
try {
long duration=(long) (Math.random()*10);
System.out.println(sender+"_"+title+" ReportGenerator:Generating a report during "+duration+" seconds");
TimeUnit.SECONDS.sleep(duration);
} catch (Exception e) {
e.printStackTrace();
}
String ret=sender+":"+title;
return ret;
}
}
public class ReportRequest implements Runnable {
private String name;
private CompletionService<String> service;
public ReportRequest(String name, CompletionService<String> service) {
super();
this.name = name;
this.service = service;
}
@Override
public void run() {
ReportGenerator generator=new ReportGenerator(name, "Reporter");
service.submit(generator);
}
}
public class ReportProcessor implements Runnable {
private CompletionService<String> service;
private boolean end;
public ReportProcessor(CompletionService<String> service) {
super();
this.service = service;
this.end = false;
}
@Override
public void run() {
while (!end) {
try {
Future<String> future = service.poll(20, TimeUnit.SECONDS);
if (future != null) {
String report = future.get();
System.out.println("ReportReceiver: Report Received:"+ report);
}
} catch (Exception e) {
e.printStackTrace();
}
}
System.out.println("ReportSender: End");
}
public void setEnd(boolean end){
this.end=end;
}
}
public class CompletionMain {
public static void main(String[] args) {
ExecutorService service=Executors.newCachedThreadPool();
CompletionService<String> service2=new ExecutorCompletionService<String>(service); ReportRequest faceRequest=new ReportRequest("face", service2);
ReportRequest onlineRequest=new ReportRequest("online", service2);
Thread faceThread=new Thread(faceRequest);
Thread onlineThread=new Thread(onlineRequest); ReportProcessor processor=new ReportProcessor(service2);
Thread senderThread=new Thread(processor); System.out.println("Main: Starting the Threads");
faceThread.start();
onlineThread.start();
senderThread.start(); try {
System.out.println("Main: waiting for the report generator");
faceThread.join();
onlineThread.join();
} catch (Exception e) {
e.printStackTrace();
} System.out.println("Main:Shuting down the executor");
service.shutdown();
try {
service.awaitTermination(1, TimeUnit.DAYS);
} catch (Exception e) {
e.printStackTrace();
}
processor.setEnd(true);
System.out.println("Main:Ends");
}
}

    《Java并发编程实践》一书6.3.5节CompletionService:Executor和BlockingQueue,有这样一段话:

  "如果向Executor提交了一组计算任务,并且希望在计算完成后获得结果,那么可以保留与每个任务关联的Future,然后反复使用get方法,同时将参数timeout指定为0,从而通过轮询来判断任务是否完成。这种方法虽然可行,但却有些繁琐。幸运的是,还有一种更好的方法:完成服务 CompletionService。"

  这是什么意思呢?我们通过一个例子,分别使用繁琐的做法和CompletionService来完成,清晰的对比能让我们更好的理解上面的一段话和CompletionService这个API提供的初衷。考虑这样的场景,有5个Callable任务分别返回5个整数,然后我们在main方法中按照各个任务完成的先后顺序,在控制台打印返回结果。

public class ReturnAfterSleepCallable implements Callable<Integer>
{
private int sleepSeconds;
private int returnValue;
public ReturnAfterSleepCallable(int sleepSeconds, int returnValue)
{
this.sleepSeconds = sleepSeconds;
this.returnValue = returnValue;
}
@Override
public Integer call() throws Exception
{
System.out.println("begin to execute.");
TimeUnit.SECONDS.sleep(sleepSeconds);
System.out.println("end to execute.");
return returnValue;
}
}

  这个任务会接受2个参数,睡眠指定的时间后,返回指定的结果。睡眠时间越短,意味着任务越先执行完成。

1.繁琐的做法

  通过一个List来保存每个任务返回的Future,然后轮询这些Future,直到每个Future都已完成。我们不希望出现因为排在前面的任务阻塞导致后面先完成的任务的结果没有及时获取的情况,所以在调用get方式时,需要将超时时间设置为0。

public class TraditionalTest{
public static void main(String[] args)
{
int taskSize = 5;
ExecutorService executor = Executors.newFixedThreadPool(taskSize);
List<Future<Integer>> futureList = new ArrayList<Future<Integer>>();
for (int i = 1; i <= taskSize; i++){
int sleep = taskSize - i; // 睡眠时间
int value = i; // 返回结果
// 向线程池提交任务
Future<Integer> future = executor.submit(new ReturnAfterSleepCallable(sleep, value));
// 保留每个任务的Future
futureList.add(future);
}
// 轮询,获取完成任务的返回结果
while(taskSize > 0){
for (Future<Integer> future : futureList){
Integer result = null;
try{
result = future.get(0, TimeUnit.SECONDS);
} catch (InterruptedException e){
e.printStackTrace();
} catch (ExecutionException e){
e.printStackTrace();
} catch (TimeoutException e){
// 超时异常需要忽略,因为我们设置了等待时间为0,只要任务没有完成,就会报该异常
}
// 任务已经完成
if(result != null) {
System.out.println("result=" + result);
// 从future列表中删除已经完成的任务
futureList.remove(future);
taskSize--;
//此处必须break,否则会抛出并发修改异常。(也可以通过将futureList声明为CopyOnWriteArrayList类型解决)
break; // 进行下一次while循环
}
}
}
// 所有任务已经完成,关闭线程池
System.out.println("all over.");
executor.shutdown();
}
}

  可见轮询future列表非常的复杂,而且还有很多异常需要处理,TimeOutException异常需要忽略;还要通过双重循环和break,防止遍历集合的过程中,出现并发修改异常。这么多需要考虑的细节,程序员很容易犯错。

2.使用CompletionService

public class CompletionServiceTest{
public static void main(String[] args) {
int taskSize = 5;
ExecutorService executor = Executors.newFixedThreadPool(taskSize);
// 构建完成服务
CompletionService<Integer> completionService = newExecutorCompletionService<Integer>( executor);
for (int i = 1; i <= taskSize; i++){
int sleep = taskSize - i; // 睡眠时间
int value = i; // 返回结果
// 向线程池提交任务
completionService
.submit(new ReturnAfterSleepCallable(sleep, value));
}
// 按照完成顺序,打印结果
for (int i = 0; i < taskSize; i++){
try{
System.out.println(completionService.take().get());
} catch (InterruptedException e){
e.printStackTrace();
} catch (ExecutionException e){
e.printStackTrace();
}
}
// 所有任务已经完成,关闭线程池
System.out.println("all over.");
executor.shutdown();
}
}

  可见使用CompletionService不会有TimeOutExeception的问题,不用遍历future列表,不用担心并发修改异常。

3. CompletionService和ExecutorCompletionService的实现

  JDK源码中CompletionService的javadoc说明如下:

/**
* A service that decouples the production of new asynchronous tasks
* from the consumption of the results of completed tasks. Producers
* <tt>submit</tt> tasks for execution. Consumers <tt>take</tt>
* completed tasks and process their results in the order they
* complete.
*/

  也就是说, CompletionService实现了生产者提交任务和消费者获取结果的解耦,生产者和消费者都不用关心任务的完成顺序,由 CompletionService来保证,消费者一定是按照任务完成的先后顺序来获取执行结果。ExecutorCompletionService是CompletionService的实现,融合了线程池Executor和阻塞队列BlockingQueue的功能。

public ExecutorCompletionService(Executor executor) {
if (executor == null)
throw new NullPointerException();
this.executor = executor;
this.aes = (executor instanceof AbstractExecutorService) ?
(AbstractExecutorService) executor : null;
this.completionQueue = new LinkedBlockingQueue<Future<V>>();
}

  到这里可以推测,按照任务的完成顺序获取结果,就是通过阻塞队列实现的,阻塞队列刚好具有这样的性质:阻塞和有序。ExecutorCompletionService 任务的提交和执行都是委托给Executor来完成。当提交某个任务时,该任务首先将被包装为一个QueueingFuture

public Future<V> submit(Callable<V> task) {
if (task == null) throw new NullPointerException();
RunnableFuture<V> f = newTaskFor(task);
executor.execute(new QueueingFuture(f));
return f;
}

  QueueingFuture 是 FutureTask 的一个子类,通过改写 FutureTask 类的 done 方法,可以实现当任务完成时,将结果放入到 BlockingQueue 中。

/**
* FutureTask extension to enqueue upon completion
*/
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;
}

  这里简单说明下:FutureTask.done(),这个方法默认什么都不做,就是一个回调,当提交的线程池中的任务完成时,会被自动调 用。这也就说时候,当任务完成的时候,会自动执行QueueingFuture.done()方法,将返回结果加入到阻塞队列中,加入的顺序就是任务完成 的先后顺序。

CompletionService 简介的更多相关文章

  1. CompletionService简介、原理以及小案例

    博客1:http://www.oschina.net/question/12_11255 博客2: CompletionService简介 CompletionService与ExecutorServ ...

  2. Java并发编程系列之二十八:CompletionService

    CompletionService简介 CompletionService与ExecutorService类似都可以用来执行线程池的任务,ExecutorService继承了Executor接口,而C ...

  3. Callable与Future、FutureTask的学习 & ExecutorServer 与 CompletionService 学习 & Java异常处理-重要

    Callable是Java里面与Runnable经常放在一起说的接口. Callable是类似于Runnable的接口,实现Callable接口的类和实现Runnable的类都是可被其他线程执行的任务 ...

  4. JUC——JUC开发简介(一)

    前言 JUC是Java5.0开始提供的一组专门实现多线程并发处理的开发框架,利用JUC开发架构可以有效的解决实际线程项目开发之中出现的死锁.阻塞.资源访问与公平机制. 此笔记主要记录java.util ...

  5. ASP.NET Core 1.1 简介

    ASP.NET Core 1.1 于2016年11月16日发布.这个版本包括许多伟大的新功能以及许多错误修复和一般的增强.这个版本包含了多个新的中间件组件.针对Windows的WebListener服 ...

  6. MVVM模式和在WPF中的实现(一)MVVM模式简介

    MVVM模式解析和在WPF中的实现(一) MVVM模式简介 系列目录: MVVM模式解析和在WPF中的实现(一)MVVM模式简介 MVVM模式解析和在WPF中的实现(二)数据绑定 MVVM模式解析和在 ...

  7. Cassandra简介

    在前面的一篇文章<图形数据库Neo4J简介>中,我们介绍了一种非常流行的图形数据库Neo4J的使用方法.而在本文中,我们将对另外一种类型的NoSQL数据库——Cassandra进行简单地介 ...

  8. REST简介

    一说到REST,我想大家的第一反应就是“啊,就是那种前后台通信方式.”但是在要求详细讲述它所提出的各个约束,以及如何开始搭建REST服务时,却很少有人能够清晰地说出它到底是什么,需要遵守什么样的准则. ...

  9. Microservice架构模式简介

    在2014年,Sam Newman,Martin Fowler在ThoughtWorks的一位同事,出版了一本新书<Building Microservices>.该书描述了如何按照Mic ...

随机推荐

  1. XML配置文件的命名空间与Spring配置文件中的头

    一直以来,写Spring配置文件,都是把其他配置文件的头拷贝过来,最多改改版本号,也不清楚哪些是需要的,到底是干嘛的.今天整理一下,拒绝再无脑copy. 一.Spring配置文件常见的配置头 < ...

  2. crontab的定时任务不能自动执行,但是手动执行脚本一直能成功

    crontab 问题小记: 环境变量问题, 养成良好的习惯, 在脚本开头export PATH 原因是 crontab 执行定时任务时,用的不是系统环境变量,而是自己的环境变量,可以把 echo $P ...

  3. Spark踩坑记——共享变量

    [TOC] 前言 Spark踩坑记--初试 Spark踩坑记--数据库(Hbase+Mysql) Spark踩坑记--Spark Streaming+kafka应用及调优 在前面总结的几篇spark踩 ...

  4. 在Android中使用am和input命令在实际使用中的注意事项以及小技巧

    在Android使用到am和进行一些操作是非常方便的,比如一个重复自动的操作,具体用来实现一些什么是看个人需求了,接下来说对于am和input的使用. 本文适用于已经大概去了解了am和input的朋友 ...

  5. 程序员的基本功之Java集合的实现细节

    1.Set集合与Map 仔细对比观察上面Set下和Map下的接口名,不难发现它们如此的相似,必有原因 如果只考察Map的Key会发现,它们不可以重复,没有顺序,也就是说把Map的所有的Key集中起来就 ...

  6. 老李推荐:第6章8节《MonkeyRunner源码剖析》Monkey原理分析-事件源-事件源概览-小结

    老李推荐:第6章8节<MonkeyRunner源码剖析>Monkey原理分析-事件源-事件源概览-小结   本章我们重点围绕处理网络过来的命令的MonkeySourceNetwork这个事 ...

  7. JS弹出框

    一.JS三种最常见的对话框 1.alert()警告框      alert是警告框,只有一个按钮"确定"无返回值,警告框经常用于确保用户可以得到某些信息.当警告框出现后,用户需要点 ...

  8. JavaScript基础学习(四)—Object

    一.Object的基本操作 1.对象的创建      在JavaScript中,创建对象的方式有两种:构造函数和对象字面量.      (1)构造函数 var person = new Object( ...

  9. 使用Microsoft SQL Server Migration Assistant for Oracle迁移数据库

    前言:使用Microsoft SQL Server Migration Assistant for Oracle迁移Oracle数据库到SqlServer数据库. 准备:Oracle11g.SqlSe ...

  10. 使用JS实现鼠标悬浮切换显示

    实现的是在鼠标悬停在不同链接上,在同一位置切换显示想要显示的内容 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional// ...