Java并发编程的艺术(九)——批量获取多条线程的执行结果

当向线程池提交callable任务后,我们可能需要一次性获取所有返回结果,有三种处理方法。

方法一:自己维护返回结果

// 创建一个线程池
ExecutorService executorService = Executors.newFixedThreadPool(10);

// 存储执行结果的List
List<Future<String>> results = new ArrayList<Future<String>>();

// 提交10个任务
for ( int i=0; i<10; i++ ) {
    Future<String> result = executorService.submit( new Callable<String>(){
        public String call(){
            int sleepTime = new Random().nextInt(1000);
            Thread.sleep(sleepTime);
            return "线程"+i+"睡了"+sleepTime+"秒";
        }
    } );
    // 将执行结果存入results中
    results.add( result );
}

// 获取10个任务的返回结果
for ( int i=0; i<10; i++ ) {
    // 获取包含返回结果的future对象
    Future<String> future = results.get(i);
    // 从future中取出执行结果(若尚未返回结果,则get方法被阻塞,直到结果被返回为止)
    String result = future.get();
    System.out.println(result);
}

此方法的弊端:

  1. 需要自己创建容器维护所有的返回结果,比较麻烦;
  2. 从list中遍历的每个Future对象并不一定处于完成状态,这时调用get()方法就会被阻塞住,如果系统是设计成每个线程完成后就能根据其结果继续做后面的事,这样对于处于list后面的但是先完成的线程就会增加了额外的等待时间。

方法二:使用ExecutorService的invokeAll函数

本方法能解决第一个弊端,即并不需要自己去维护一个存储返回结果的容器。当我们需要获取线程池所有的返回结果时,只需调用invokeAll函数即可。 但是,这种方式需要你自己去维护一个用于存储任务的容器。

// 创建一个线程池
ExecutorService executorService = Executors.newFixedThreadPool(10);

// 创建存储任务的容器
List<Callable<String>> tasks = new ArrayList<Callable<String>>();

// 提交10个任务
for ( int i=0; i<10; i++ ) {
    Callable<String> task = new Callable<String>(){
        public String call(){
            int sleepTime = new Random().nextInt(1000);
            Thread.sleep(sleepTime);
            return "线程"+i+"睡了"+sleepTime+"秒";
        }
    };
    executorService.submit( task );
    // 将task添加进任务队列
    tasks.add( task );
}

// 获取10个任务的返回结果
List<Future<String>> results = executorService.invokeAll( tasks );

// 输出结果
for ( int i=0; i<10; i++ ) {
    // 获取包含返回结果的future对象
    Future<String> future = results.get(i);
    // 从future中取出执行结果(若尚未返回结果,则get方法被阻塞,直到结果被返回为止)
    String result = future.get();
    System.out.println(result);
}

方法三:使用CompletionService

CompletionService内部维护了一个阻塞队列,只有执行完成的任务结果才会被放入该队列,这样就确保执行时间较短的任务率先被存入阻塞队列中。

ExecutorService exec = Executors.newFixedThreadPool(10);

final BlockingQueue<Future<Integer>> queue = new LinkedBlockingDeque<Future<Integer>>(  
                10);  
        //实例化CompletionService  
        final CompletionService<Integer> completionService = new ExecutorCompletionService<Integer>(  
                exec, queue); 

// 提交10个任务
for ( int i=0; i<10; i++ ) {
    executorService.submit( new Callable<String>(){
        public String call(){
            int sleepTime = new Random().nextInt(1000);
            Thread.sleep(sleepTime);
            return "线程"+i+"睡了"+sleepTime+"秒";
        }
    } );
}

// 输出结果
for ( int i=0; i<10; i++ ) {
    // 获取包含返回结果的future对象(若整个阻塞队列中还没有一条线程返回结果,那么调用take将会被阻塞,当然你可以调用poll,不会被阻塞,若没有结果会返回null,poll和take返回正确的结果后会将该结果从队列中删除)
    Future<String> future = completionService.take();
    // 从future中取出执行结果,这里存储的future已经拥有执行结果,get不会被阻塞
    String result = future.get();
    System.out.println(result);
}

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏小勇DW3

LockSupport的源码实现原理以及应用

如果只是LockSupport在使用起来比Object的wait/notify简单,

30020
来自专栏haifeiWu与他朋友们的专栏

FutureTask源码分析

FutureTask:一个可取消的异步任务执行类,这个类提供了Future接口的基本实现,主要有以下功能:

17930
来自专栏雨尘分享

5.Block的内存管理 内存管理内存管理block的循环引用

18340
来自专栏Android 研究

Android Handler机制12之Callable、Future和FutureTask

说到Callable就不能不说下java.lang.Runnable,它是一个接口,它只声明了一个run()方法,由于这个run()方法的返回值是void的,所...

15420
来自专栏Java帮帮-微信公众号-技术文章全总结

Java多线程详解6【面试+工作】

Java多线程详解【面试+工作】 Java线程:新特征-原子量 所谓的原子量即操作变量的操作是“原子的”,该操作不可再分,因此是线程安全的。 为何要使用原子变量...

30070
来自专栏小灰灰

Java并发学习之四种线程创建方式的实现与对比

线程创建的几种方式 在并发编程中,最基本的就是创建线程了,那么一般的创建姿势是怎样的,又都有些什么区别 一般来讲线程创建有四种方式: 继承Thread 实现R...

44180
来自专栏IT技术精选文摘

Java并发包类总览

并发容器 这些容器的关键方法大部分都实现了线程安全的功能,却不使用同步关键字(synchronized)。值得注意的是Queue接口本身定义的几个常用方法的区...

236100
来自专栏java达人

多线程设计模式解读2—Promise(承诺)模式

上次我们讲到多线程设计模式的Guarded Suspension(保护性暂挂模式),Guarded Suspension是条件未满足时线程一直处于等待状态,直到...

10230
来自专栏无题

JAVA多线程构件(java.util.concurrent包下高级工具)

Java1.5提供了一个非常高效实用的多线程包:java.util.concurrent, 提供了大量高级工具,可以帮助开发者编写高效、易维护、结构清晰的Jav...

1.2K70
来自专栏Spark学习技巧

java线程池模型

一, 线程池与普通线程 1 普通线程 Java实现多线程,常见的有以下三种方式: 1 继承Thread,重写该类的run()方法 2 实现Runnable 实现...

24370

扫码关注云+社区

领取腾讯云代金券