相信大家都知道,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使用的更多相关文章

  1. Java 并发编程实践基础 读书笔记: 第三章 使用 JDK 并发包构建程序

    一,JDK并发包实际上就是指java.util.concurrent包里面的那些类和接口等 主要分为以下几类: 1,原子量:2,并发集合:3,同步器:4,可重入锁:5,线程池 二,原子量 原子变量主要 ...

  2. Java并发程序设计(四)JDK并发包之同步控制

    JDK并发包之同步控制 一.重入锁 重入锁使用java.util.concurrent.locks.ReentrantLock来实现.示例代码如下: public class TryReentrant ...

  3. Java多线程--JDK并发包(2)

    Java多线程--JDK并发包(2) 线程池 在使用线程池后,创建线程变成了从线程池里获得空闲线程,关闭线程变成了将线程归坏给线程池. JDK有一套Executor框架,大概包括Executor.Ex ...

  4. Java多线程--JDK并发包(1)

    Java多线程--JDK并发包(1) 之前介绍了synchronized关键字,它决定了额一个线程是否可以进入临界区:还有Object类的wait()和notify()方法,起到线程等待和唤醒作用.s ...

  5. 3 JDK并发包

    JDK内部提供了大量实用的API和框架.本章主要介绍这些JDK内部功能,主要分为3大部分: 首先,介绍有关同步控制的工具,之前介绍的synchronized就是一种同步控制手段,将介绍更加丰富的多线程 ...

  6. Java 并发包中的高级同步工具

    Java 并发包中的高级同步工具 Java 中的并发包指的是 java.util.concurrent(简称 JUC)包和其子包下的类和接口,它为 Java 的并发提供了各种功能支持,比如: 提供了线 ...

  7. 第3章 JDK并发包(五)

    3.3 不要重复发明轮子:JDK的并发容器 3.3.1 超好用的工具类:并发集合简介 JDK提供的这些容器大部分在java.util.concurrent包中. ConcurrentHashMap:这 ...

  8. 第3章 JDK并发包(三)

    3.2 线程复用:线程池 一种最为简单的线程创建和回收的方法类似如下代码: new Thread(new Runnable() { @Override public void run() { // d ...

  9. JDK并发包二

    JDK并发包二 线程复用--线程池 在线程池中,总有那么几个活跃的线程,当程序需要线程时可以从池子中随便拿一个控线程,当程序执行完毕,线程不关闭,而是将这个线程退会到池子,等待使用. JDK提供了一套 ...

随机推荐

  1. scikit-FEM-例2-用Morley元在方形区域上解板弯曲问题

    """ Author: kinnala Solve the Kirchhoff plate bending problem in a unit square with c ...

  2. RxSwift学习笔记2:Observable/生命周期/Event/oneNext/onError/onCompleted/

     Observable 是 Rx 的根基 官网:http://reactivex.io/ github地址:https://github.com/ReactiveX/RxSwift Observabl ...

  3. 使用NetHogs监控进程网络使用情况

    Nethogs 是一个终端下的网络流量监控工具,它的特别之处在于可以显示每个进程的带宽占用情况,这样可以更直观获取网络使用情况.它支持 IPv4 和 IPv6 协议.支持本地网卡及 PPP 链接. 使 ...

  4. poj1321 DFS

    棋盘问题 Time Limit: 1000 MS Memory Limit: 10000 KB 64-bit integer IO format: %I64d , %I64u Java class n ...

  5. AJPFX平台讲述买卖、点差、单位,外汇的交易时间以及外汇交易者的参与者

    AJPFX平台讲解:买(多).卖(空).点差.单位 外汇保交易也就是通过外汇的升值和贬值来赚取利润.以EURUSD(欧元/美元)为例.假设目前价格为1.3820左右,即1欧元兑换1.3820美元.这个 ...

  6. [WC2005]双面棋盘(线段树+并查集)

    线段树+并查集维护连通性. 好像 \(700ms\) 的时限把我的常数超级大的做法卡掉了, 必须要开 \(O_2\) 才行. 对于线段树的每一个结点都开左边的并查集,右边的并查集,然后合并. \(Co ...

  7. Mac 下查看端口是否被占用

    1. lsof -i :8080 2. netstat -anp tcp | grep 8080 3. nc -w 10 -n -z 127.0.0.1 8070-8090

  8. GC日志时间分析

    在GC日志里,一条完整的GC日志记录最后,会带有本次GC所花费的时间,如下面这一条新生代GC: [GC [DefNew: 3298K->149K(5504K), secs] [Times: us ...

  9. 【spring cloud】服务启动后正常,但是无法上线,一直处于down状态

    spring cloud eureka 如果出现某个应用实例 down(1), 说明 spring admin 健康检测没有通过导致 eureka 注册中心不会把这个实例从列表中删除掉. 这样所有使用 ...

  10. spring cloud 学习(6) - zuul 微服务网关

    微服务架构体系中,通常一个业务系统会有很多的微服务,比如:OrderService.ProductService.UserService...,为了让调用更简单,一般会在这些服务前端再封装一层,类似下 ...