JDK并发包中ExecutorCompletionService使用
相信大家都知道,jdk中ExecutorService是并发编程时使用很频繁的接口,并且使用很方便,那么想在有这么一个场景:
一批任务使用线程池处理,并且需要获得结果,但是不关心任务执行结束后输出结果的先后顺序,应该如何实现?大多数人可能会想到,将任务作为一个Callable,然后调用submit塞入ExecutorService中,返回Future,然后遍历Future,依次获得结果不就行了吗?
那么,大家是否想过,这样有两个不好的点:1.调用submit后需要将Future存储;2.遍历Future的list时,get()方法时阻塞的,就算使用get(long timeout, TimeUnit unit)方法,避免不了需要通过while来循环获取结果
其实如果不关心任务返回的先后顺序,那么还有一个更方便的线程池,也就是今天要分享,使用ExecutorCompletionService避免以上两个问题,并且编码简单,容易理解:
public class CompletionServiceTest {
//初始化固定大小为3的线程池
private static ExecutorService executor = Executors.newFixedThreadPool(3);
public static void main(String[] args) {
List<CompletionServiceTask> tasks = new ArrayList<>();
//新建3个任务
CompletionServiceTask task1 = new CompletionServiceTask(6000);
CompletionServiceTask task2 = new CompletionServiceTask(4000);
CompletionServiceTask task3 = new CompletionServiceTask(2000);
tasks.add(task1);
tasks.add(task2);
tasks.add(task3);
addTask(tasks);
}
public static void addTask(List<CompletionServiceTask> tasks) {
//以executor为构造器的参数,新建一个ExecutorCompletionService线程池
ExecutorCompletionService<Integer> completionService = new ExecutorCompletionService<>(executor);
for (CompletionServiceTask task : tasks) {
//提交任务
completionService.submit(task);
System.out.println("添加任务 :" + task.time);
}
for (CompletionServiceTask task : tasks) {
try {
Integer time = completionService.take().get();
System.out.println("任务返回结果:" + time);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
//关闭线程池
executor.shutdown();
}
static class CompletionServiceTask implements Callable<Integer> {
public int time;
public CompletionServiceTask(int time) {
this.time = time;
}
@Override
public Integer call() throws Exception {
Thread.sleep(time);
return time;
}
}
}
上述代码运行结果:
添加任务 :6000
添加任务 :4000
添加任务 :2000
任务返回结果:2000
任务返回结果:4000
任务返回结果:6000
从结果可以看出,虽然6000是第一个添加进去,但是最先返回的确实2000,因此我们可以看出,并非先添加的任务先返回,而是最先执行结束的任务先返回,这样的好处就是不用为了等待前面的任务,导致后续的阻塞。
原因分析:查看ExecutorCompletionService的源码:
private final BlockingQueue<Future<V>> completionQueue;
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;
}
其中,有一个类型为BlockingQueue的全局变量completionQueue(这里划重点,这个阻塞队列就是用来存储线程池执行返回结果的),submit()方法会将Callable封装成一个RunnableFuture,然后将其塞入QueueingFuture中,交给executor执行,我们再看一下QueueingFuture(FutureTask的一个子类):
private final BlockingQueue<Future<V>> completionQueue;
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()方法,将结果放入上面的BlockingQueue中,所以加入的顺序就是任务执行完成的先后顺序。
JDK并发包中ExecutorCompletionService使用的更多相关文章
- Java 并发编程实践基础 读书笔记: 第三章 使用 JDK 并发包构建程序
一,JDK并发包实际上就是指java.util.concurrent包里面的那些类和接口等 主要分为以下几类: 1,原子量:2,并发集合:3,同步器:4,可重入锁:5,线程池 二,原子量 原子变量主要 ...
- Java并发程序设计(四)JDK并发包之同步控制
JDK并发包之同步控制 一.重入锁 重入锁使用java.util.concurrent.locks.ReentrantLock来实现.示例代码如下: public class TryReentrant ...
- Java多线程--JDK并发包(2)
Java多线程--JDK并发包(2) 线程池 在使用线程池后,创建线程变成了从线程池里获得空闲线程,关闭线程变成了将线程归坏给线程池. JDK有一套Executor框架,大概包括Executor.Ex ...
- Java多线程--JDK并发包(1)
Java多线程--JDK并发包(1) 之前介绍了synchronized关键字,它决定了额一个线程是否可以进入临界区:还有Object类的wait()和notify()方法,起到线程等待和唤醒作用.s ...
- 3 JDK并发包
JDK内部提供了大量实用的API和框架.本章主要介绍这些JDK内部功能,主要分为3大部分: 首先,介绍有关同步控制的工具,之前介绍的synchronized就是一种同步控制手段,将介绍更加丰富的多线程 ...
- Java 并发包中的高级同步工具
Java 并发包中的高级同步工具 Java 中的并发包指的是 java.util.concurrent(简称 JUC)包和其子包下的类和接口,它为 Java 的并发提供了各种功能支持,比如: 提供了线 ...
- 第3章 JDK并发包(五)
3.3 不要重复发明轮子:JDK的并发容器 3.3.1 超好用的工具类:并发集合简介 JDK提供的这些容器大部分在java.util.concurrent包中. ConcurrentHashMap:这 ...
- 第3章 JDK并发包(三)
3.2 线程复用:线程池 一种最为简单的线程创建和回收的方法类似如下代码: new Thread(new Runnable() { @Override public void run() { // d ...
- JDK并发包二
JDK并发包二 线程复用--线程池 在线程池中,总有那么几个活跃的线程,当程序需要线程时可以从池子中随便拿一个控线程,当程序执行完毕,线程不关闭,而是将这个线程退会到池子,等待使用. JDK提供了一套 ...
随机推荐
- Trie树的数组实现原理
Trie(Retrieval Tree)又称前缀树,可以用来保存多个字符串,并且非常便于查找.在trie中查找一个字符串的时间只取决于组成该串的字符数,与树的节点数无关.因此,它的查找速度通常比二叉搜 ...
- hdu 4940 数据太水...
http://acm.hdu.edu.cn/showproblem.php?pid=4940 给出一个有向强连通图,每条边有两个值分别是破坏该边的代价和把该边建成无向边的代价(建立无向边的前提是删除该 ...
- centos救援模式实验笔记
1. 首先在BIOS中把启动选项设置成DVD光驱启动或者USB启动也是可以的 2. 从光盘启动之后再出现的选项中选择“Rescue installed system”然后按回车确认,具体图下图: ...
- EntityFramework Core 学习扫盲
0. 写在前面 1. 建立运行环境 2. 添加实体和映射数据库 1. 准备工作 2. Data Annotations 3. Fluent Api 3. 包含和排除实体类型 1. Data Annot ...
- CentOS 7安装fail2ban+Firewalld防止SSH爆破与CC攻击
准备工作 1.检查Firewalld是否启用 #如果您已经安装iptables建议先关闭 service iptables stop #查看Firewalld状态 firewall-cmd --sta ...
- ASP.NET WebAPI (反)序列化用[SerializableAttribute]修饰的类的一个坑
发现问题 在 ASP.NET WebAPI 项目中,有这样的 ViewModel 类: [Serializable] class Product { public int Id { get; set; ...
- 6.翻译:EF基础系列---什么是EF中的实体?
原文地址:http://www.entityframeworktutorial.net/basics/what-is-entity-in-entityframework.aspx EF中的实体就是继承 ...
- CopyOnWriteArrayList源码解析(1)
此文已由作者赵计刚授权网易云社区发布. 欢迎访问网易云社区,了解更多网易技术产品运营经验. 注:在看这篇文章之前,如果对ArrayList底层不清楚的话,建议先去看看ArrayList源码解析. ht ...
- [leetcode.com]算法题目 - Remove Duplicates from Sorted List
Given a sorted linked list, delete all duplicates such that each element appear only once. For examp ...
- Shell命令之文本操作
前言 在Linux中,文本处理操作是最常见的,应用非常广泛,如果能熟练掌握,可以大大提高开发效率. awk/sed/grep是文本操作领域的“三剑客”,学会了这3个命令就可以应对绝大多数文本处理场景. ...