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提供了一套 ...
随机推荐
- scikit-FEM-例2-用Morley元在方形区域上解板弯曲问题
""" Author: kinnala Solve the Kirchhoff plate bending problem in a unit square with c ...
- RxSwift学习笔记2:Observable/生命周期/Event/oneNext/onError/onCompleted/
Observable 是 Rx 的根基 官网:http://reactivex.io/ github地址:https://github.com/ReactiveX/RxSwift Observabl ...
- 使用NetHogs监控进程网络使用情况
Nethogs 是一个终端下的网络流量监控工具,它的特别之处在于可以显示每个进程的带宽占用情况,这样可以更直观获取网络使用情况.它支持 IPv4 和 IPv6 协议.支持本地网卡及 PPP 链接. 使 ...
- poj1321 DFS
棋盘问题 Time Limit: 1000 MS Memory Limit: 10000 KB 64-bit integer IO format: %I64d , %I64u Java class n ...
- AJPFX平台讲述买卖、点差、单位,外汇的交易时间以及外汇交易者的参与者
AJPFX平台讲解:买(多).卖(空).点差.单位 外汇保交易也就是通过外汇的升值和贬值来赚取利润.以EURUSD(欧元/美元)为例.假设目前价格为1.3820左右,即1欧元兑换1.3820美元.这个 ...
- [WC2005]双面棋盘(线段树+并查集)
线段树+并查集维护连通性. 好像 \(700ms\) 的时限把我的常数超级大的做法卡掉了, 必须要开 \(O_2\) 才行. 对于线段树的每一个结点都开左边的并查集,右边的并查集,然后合并. \(Co ...
- Mac 下查看端口是否被占用
1. lsof -i :8080 2. netstat -anp tcp | grep 8080 3. nc -w 10 -n -z 127.0.0.1 8070-8090
- GC日志时间分析
在GC日志里,一条完整的GC日志记录最后,会带有本次GC所花费的时间,如下面这一条新生代GC: [GC [DefNew: 3298K->149K(5504K), secs] [Times: us ...
- 【spring cloud】服务启动后正常,但是无法上线,一直处于down状态
spring cloud eureka 如果出现某个应用实例 down(1), 说明 spring admin 健康检测没有通过导致 eureka 注册中心不会把这个实例从列表中删除掉. 这样所有使用 ...
- spring cloud 学习(6) - zuul 微服务网关
微服务架构体系中,通常一个业务系统会有很多的微服务,比如:OrderService.ProductService.UserService...,为了让调用更简单,一般会在这些服务前端再封装一层,类似下 ...