相信大家都知道,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. bootstrap增删改查

    <!DOCTYPE html><html> <head> <meta charset="utf-8" /> <title> ...

  2. Python基础的练习

    ---恢复内容开始--- 简单输入输出交互. >>> name='Jame' >>> print('Hi,%s.'%name) Hi,Jame. >>& ...

  3. 在EF中使用Expression自动生成p=>new Entity(){X="",Y="",..}格式的Lambda表达式灵活实现按需更新

    一.基本介绍      回忆:最早接触Expression是在学校接触到EF的时候,发现where方法里的参数是Expression<Func<T,bool>>这么一个类型,当 ...

  4. 多用户在线人数监听(基于TomCat)

    服务器Servlet端 package com.sxt.mvcpro.servlet; import java.io.IOException; import java.util.HashSet; im ...

  5. ZJOI Round2游记

    虽然一试很惨但是二试还是要来玩一下的 Day 0 到余姚了,然后到余姚边上的宾馆住来下来 顺便一问老师们对边上是不是有什么误解-- 吃完晚饭就回宾馆颓了 话说半夜真刺激--住隔壁那一位手突然骨折了,本 ...

  6. DZY Loves Math(莫比乌斯反演)

    \(x=p_1^{\alpha_1}p_2^{\alpha_2}...p_c^{\alpha_c}\) \(f(x)=\max(\alpha_1,\alpha_2,...,\alpha_c)\) \( ...

  7. 尝试利用slmail的漏洞来getshell

    作者:Joe   本文属于Arctic shell原创内容计划文章,转载请注明原文地址! 二进制,计算机才可以理解的低级语言,简单来说它是一种信号,用电信号为例,0就是断电,而1就是有电,这样子010 ...

  8. Android逆向——smali复杂类解析

    i春秋作家:HAI_ 之前在Android逆向——初识smali与java类中讲解了基本的HelloWorld和简单类.这节课就要进一步深入.如果能够耐下心来分析一定会有所收获.——写给自己和后来人. ...

  9. centos清华源地址,ubuntu阿里云源

    清华这个源挺好用,替换原先/etc/yum.repos.d/CentOS-Base.repo, 然后运行 sudo yum makecache 生成缓存 centos6 [base] name=Cen ...

  10. 1-1 Vue的介绍

    简单介绍Vue Vue (读音 /vjuː/,类似于 view) 是一套用于构建用户界面的渐进式框架.与其它大型框架不同的是,Vue 被设计为可以自底向上逐层应用.Vue 的核心库只关注视图层,不仅易 ...