前提概要

在开发过程中在使用多线程进行并行处理一些事情的时候,大部分场景在处理多线程并行执行任务的时候,可以通过List添加Future来获取执行结果,有时候我们是不需要获取任务的执行结果的,方便后面引出ExecutorCompletionService。

CompletionService的介绍

  • CompletionService 接口是一个独立的接口,并没有扩展ExecutorService 。 其默认实现类是ExecutorCompletionService。

  • 接口CompletionService 的功能是:以异步的方式一边执行未完成的任务,一边记录、处理已完成任务的结果。从而可以将任务的执行与处理任务的执行结果分离开来。

CompletionService的实现原理

  • CompletionService就是监视着 Executor线程池执行的任务,用BlockingQueue将完成的任务的结果存储下来。

  • 要不断遍历与每个任务关联的Future,然后不断去轮询,判断任务是否已经完成,功能比较繁琐。

public interface CompletionService<V> {
Future<V> submit(Callable<V> task);
Future<V> submit(Runnable task, V result);
Future<V> take() throws InterruptedException;
Future<V> poll();
Future<V> poll(long timeout, TimeUnit unit) throws InterruptedException;
}

方法摘要

提交一个 Callable 任务;一旦完成,便可以由take()、poll()方法获取

Future submit(Callable task):

提交一个 Runnable 任务,并指定计算结果;

Future submit(Runnable task, V result):

获取并移除表示下一个已完成任务的 Future,如果目前不存在这样的任务,则等待。

Future take() throws InterruptedException

获取并移除表示下一个已完成任务的 Future,如果不存在这样的任务,则返回 null。

Future poll()

获取并移除表示下一个已完成任务的 Future,如果目前不存在这样的任务,则将等待指定的时间(如果有必要)。

Future poll(long timeout, TimeUnit unit) throws InterruptedException

例子,程序提交了多个任务,但只要有一个任务完成并返回一个非空的结果,并可以忽略掉其余的任务。

 void eample(Executor e, Collection<Callable<Result>> solvers) throws InterruptedException {
CompletionService<Result> completionService = new ExecutorCompletionService<Result>(e);
int n = solvers.size();
List<Future<Result>> futures = new ArrayList<Future<Result>>(n);
Result result = null;
try {
//提交多个任务
for (Callable<Result> s : solvers)
futures.add(completionService.submit(s));
//
for (int i = 0; i < n; ++i) {
try {
//等待获取一个已经完成的任务
Result r = completionService.take().get();
//判断返回结果是否为空
if (r != null) {
result = r;
break;
}
} catch (ExecutionException ignore) {}
}
}
finally {
//取消所有任务
for (Future<Result> f : futures)
f.cancel(true);
}
if (result != null)
use(result);
}

ExecutorCompletionService的介绍

  • ExecutorCompletionService内部有一个先进先出的阻塞队列,用于保存已经执行完成的Future,通过调用它的take方法或poll方法可以获取到一个已经执行完成的Future,进而通过调用Future接口实现类的get方法获取最终的结果。

  • ExecutorCompletionService实现了CompletionService,内部通过Executor以及BlockingQueue来实现接口提出的规范,ExecutorCompletionService,提交任务后,可以按任务返回结果的先后顺序来获取各任务执行后的结果,该类实现了接口CompletionService

构造方法

  • 指定一个Executor来执行任务,存储完成的任务的完成队列是LinkedBlockingQueue ;
  • Executor由调用者传递进来,而Blocking可以使用默认的LinkedBlockingQueue,也可以由调用者传递。
ExecutorCompletionService(Executor executor):

指定了任务执行器Executor和已完成的任务队列completionQueue

ExecutorCompletionService(Executor executor, BlockingQueue<Future> completionQueue)
实现构造器
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>>();
}
  • 该接口定义了一系列方法:提交实现了Callable或Runnable接口的任务,并获取这些任务的结果。

  • 包装后提交任务的submit()方法,该类还会将提交的任务封装成QueueingFuture,这样就可以实现FutureTask.done()方法,以便于在任务执行完毕后,将结果放入阻塞队列中。

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为内部类:

在提交任务时,将任务封装成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;
}

其中,done()方法就是在任务执行完毕后,将任务放入队列中。

  • 在调用take()、poll()方法时,会从阻塞队列中获取Future对象,以取得任务执行的结果。

  • 它继承自 FutureTask,并且重写了 done 方法,其方法把任务放到我们包装线程池创建的堵塞队列里面;就是当任务执行完成后,就会被放到队列里面去了。

  • 调用其take() 方法,就是阻塞等待,等到的一定是能够获取的结果的future,然后再调用get()方法获取执行结果;

最后,如果工作中并行处理任务不需要获取结果的,我们正常使用线程池提交就可以,任务技术只要适合工作的业务场景就是好的。

☕【Java技术指南】「并发编程专题」CompletionService框架基本使用和原理探究(基础篇)的更多相关文章

  1. ☕【Java技术指南】「并发编程专题」针对于Guava RateLimiter限流器的入门到精通(含实战开发技巧)

    并发编程的三剑客 在开发高并发系统时有三剑客:缓存.降级和限流. 缓存 缓存的目的是提升系统访问速度和增大系统处理容量. 降级 降级是当服务出现问题或者影响到核心流程时,需要暂时屏蔽掉,待高峰或者问题 ...

  2. ☕【Java技术指南】「并发编程专题」Fork/Join框架基本使用和原理探究(基础篇)

    前提概述 Java 7开始引入了一种新的Fork/Join线程池,它可以执行一种特殊的任务:把一个大任务拆成多个小任务并行执行. 我们举个例子:如果要计算一个超大数组的和,最简单的做法是用一个循环在一 ...

  3. 🏆【Java技术专区】「并发编程专题」教你如何使用异步神器CompletableFuture

    前提概要 在java8以前,我们使用java的多线程编程,一般是通过Runnable中的run方法来完成,这种方式,有个很明显的缺点,就是,没有返回值.这时候,大家可能会去尝试使用Callable中的 ...

  4. ☕【Java技术指南】「JPA编程专题」让你不再对JPA技术中的“持久化型注解”感到陌生了!

    JPA的介绍分析 Java持久化API (JPA) 显著简化了Java Bean的持久性并提供了一个对象关系映射方法,该方法使您可以采用声明方式定义如何通过一种标准的可移植方式,将Java 对象映射到 ...

  5. ☕【Java深层系列】「并发编程系列」深入分析和研究MappedByteBuffer的实现原理和开发指南

    前言介绍 在Java编程语言中,操作文件IO的时候,通常采用BufferedReader,BufferedInputStream等带缓冲的IO类处理大文件,不过java nio中引入了一种基于Mapp ...

  6. ☕【Java深层系列】「并发编程系列」让我们一起探索一下CyclicBarrier的技术原理和源码分析

    CyclicBarrier和CountDownLatch CyclicBarrier和CountDownLatch 都位于java.util.concurrent这个包下,其工作原理的核心要点: Cy ...

  7. 并发编程学习笔记(5)----AbstractQueuedSynchronizer(AQS)原理及使用

    (一)什么是AQS? 阅读java文档可以知道,AbstractQueuedSynchronizer是实现依赖于先进先出 (FIFO) 等待队列的阻塞锁和相关同步器(信号量.事件,等等)提供一个框架, ...

  8. Java并发编程专题

    为了防止无良网站的爬虫抓取文章,特此标识,转载请注明文章出处.LaplaceDemon/ShiJiaqi. http://www.cnblogs.com/shijiaqi1066/p/4852149. ...

  9. Java 面试宝典!并发编程 71 道题及答案全送上!

    金九银十跳槽季已经开始,作为 Java 开发者你开始刷面试题了吗?别急,我整理了71道并发相关的面试题,看这一文就够了! 1.在java中守护线程和本地线程区别? java中的线程分为两种:守护线程( ...

随机推荐

  1. webRTC的标准与发展

    Web实时通信(WebRTC)是标准,协议和JavaScript API的集合,两者的组合可实现浏览器(对等)之间的对等音频,视频和数据共享.WebRTC无需依赖第三方插件或专有软件,而是将实时通信转 ...

  2. C++手写内存池

    引言 使用new expression为类的多个实例分配动态内存时,cookie导致内存利用率可能不高,此时我们通过实现类的内存池来降低overhead.从不成熟到巧妙优化的内存池,得益于union的 ...

  3. HCNA Routing&Switching之GVRP

    前文我们了解了不同vlan间路由相关话题,回顾请参考https://www.cnblogs.com/qiuhom-1874/p/15110336.html,今天我们来聊一聊vlan动态注册相关话题: ...

  4. appium自动化测试(4)部分方法&unitest初步使用

    捕捉弹窗 https://github.com/appium/appium/issues/968完整有截屏的例子:https://github.com/bitbar/testdroid-samples ...

  5. 又一开源项目爆火于GitHub,Android高级插件化强化实战

    一.插件化起源 插件化技术最初源于免安装运行 Apk的想法,这个免安装的 Apk 就可以理解为插件,而支持插件的 app 我们一般叫 宿主. 想必大家都知道,在 Android 系统中,应用是以 Ap ...

  6. 【Android面试查漏补缺】之事件分发机制详解

    前言 查漏补缺,查漏补缺,你不知道哪里漏了,怎么补缺呢?本文属于[Android面试查漏补缺]系列文章第一篇,持续更新中,感兴趣的朋友可以[关注+收藏]哦~ 本系列文章是对自己的前段时间面试经历的总结 ...

  7. Quartz部署Linux的一个坑

    前言 最近做了一个项目,使用Quartz做定时任务,然后部署到了Linux服务器上,但是竟然很惊奇的跑不起来,已经在阿里云上的Linux上验证无数次了,后来经过不懈努力,终于发现了问题,我自己的Lin ...

  8. CentOS后台服务管理类

    目录 一.service 后台服务管理(临时,只对当前有效) 二.chkconfig 设置后台服务的自启配置(永久) 三.CentOS7 后添加的命令:systemctl 一.service 后台服务 ...

  9. thunderbird发送纯文本邮件

    向邮件列表中发邮件时,要求邮件格式必须是纯文本格式的,在thunderbird中,邮件格式(plain text或者html格式)在[工具->账户设置->[账户名称]->通讯录]下的 ...

  10. VulnHub靶场渗透之:Gigachad

    环境搭建 VulnHub是一个丰富的实战靶场集合,里面有许多有趣的实战靶机. 本次靶机介绍: http://www.vulnhub.com/entry/gigachad-1,657/ 下载靶机ova文 ...