Java多线程-CompletionService

原创 2017年05月12日 16:03:15

原文地址 http://blog.csdn.net/qq_25806863/article/details/71743659

之前说过,线程池ThreadPoolExecutor可以调用submit方法来获取返回值Future。像下面这样:

这里先定义三个Callable,之后都用这三个:

        Callable callable1 = new Callable() {
            @Override
            public String call() throws Exception {
                Thread.sleep(5000);
                return "我是call1的返回值";
            }
        };
        Callable callable2 = new Callable() {
            @Override
            public String call() throws Exception {
                Thread.sleep(3000);
                return "我是call2的返回值";
            }
        };
        Callable callable3 = new Callable() {
            @Override
            public String call() throws Exception {
                Thread.sleep(1000);
                return "我是call3的返回值";
            }
        };

直接使用ThreadPoolExecutor的submit获取结果的使用方法是这样的:

        //声明一个线程池
        ExecutorService executor = Executors.newFixedThreadPool(5);
        //提交三个任务
        Future future1 = executor.submit(callable1);
        Future future2 = executor.submit(callable2);
        Future future2 = executor.submit(callable2);
        //开始获取返回值
        System.out.println("两个任务提交完毕,开始获取结果 "+getStringDate());
        System.out.println(future1.get()+" "+getStringDate());
        System.out.println(future2.get()+" "+getStringDate());
        System.out.println(future3.get()+" "+getStringDate());
        System.out.println("获取结果完毕 "+getStringDate());

根据之前的理解,get()方法是有阻塞性的,因为future1的任务执行时间是5秒,所以在future1.get()这行代码上会阻塞5秒,然后才会获取到结果,继续往下执行。而在5秒内future2和future3的任务已经执行完了,所以会立马得到结果。

真实输出也是这样:

这里写图片描述

明明future2和future3的任务早就执行完了,却被future1.get()方法阻塞了。

使用CompletionService可以作为一种解决方法。

CompletionService简介

CompletionService的主要功能就是一边生成任务,一边获取任务的返回值。让两件事分开执行,任务之间不会互相阻塞。

CompletionService在提交任务之后,会根据任务完成顺序来获取返回值,也就是谁先完成就返回谁的返回值。

CompletionService是一个接口:

public interface CompletionService<V> {
    Future<V> submit(Callable<V> var1);
    Future<V> submit(Runnable var1, V var2);
    Future<V> take() throws InterruptedException;
    Future<V> poll();
    Future<V> poll(long var1, TimeUnit var3) throws InterruptedException;
}

CompletionService只有一个实现类,就是ExecutorCompletionService

这里写图片描述

我这里有两个是因为用的AndroidStudio,一个是java的SDK的一个是Android的SDK的。

ExecutorCompletionService的使用

CompletionService接口一共也就定义了那么几个方法,submit方法和ExecutorService的submit没什么不同。

下面主要分析一下take()方法和poll()方法

构造方法

ExecutorCompletionService的构造方法有两个:

public ExecutorCompletionService(Executor var1)
ExecutorCompletionService(Executor var1, BlockingQueue<Future<V>> var2)

由此可见,CompletionService对任务的各种操作还是通过Executor来实现的,一般就是ThreadPoolExecutor。

下面是一个简单例子:

还是用一开始的三个Callable,这次用CompletionService来提交任务并获取结果。

//新建一个线程池executor
ExecutorService executor = Executors.newFixedThreadPool(5);
//用线程池executor新建一个CompletionService
CompletionService completionService = new ExecutorCompletionService(executor);
//用CompletionService提交任务
completionService.submit(callable1);
completionService.submit(callable2);
completionService.submit(callable3);
//用CompletionService获取结果
System.out.println("两个任务提交完毕,开始获取结果 "+getStringDate());
System.out.println(completionService.take().get()+" "+getStringDate());
System.out.println(completionService.take().get()+" "+getStringDate());
System.out.println(completionService.take().get()+" "+getStringDate());
System.out.println("获取结果完毕 "+getStringDate());

可以看下输出:

这里写图片描述

虽然提交的顺序是1,2,3,但是获取结果的时候是按任务完成顺序来获取的,所以结果是3,2,1.

take()方法

其实take()方法也是一个阻塞方法,调用这个方法时,他会一直等待直到线程池中返回一个结果,哪个任务先完成,就返回哪个任务的结果。

在上面的例子中,由于callable3是最先完成的,所以最先拿到的值就是callable3的返回值。

因为刚好提交了3个任务,调用了3次take()方法,因此刚好能拿到全部的任务的结果。

如果在调用一次take()方法,那么就会因为等不到有任务返回结果而阻塞在那里:

例如值提交一个任务,而调用两次take()方法,那么程序就会阻塞在第二个take()方法那里等待一个结果

        ExecutorService executor = Executors.newFixedThreadPool(5);
        CompletionService completionService = new ExecutorCompletionService(executor);
        completionService.submit(callable1);
        System.out.println("两个任务提交完毕,开始获取结果 "+getStringDate());

        System.out.println(completionService.take().get()+" "+getStringDate());
        System.out.println(completionService.take().get()+" "+getStringDate());

        System.out.println("获取结果完毕 "+getStringDate());

结果会一直是这样

这里写图片描述

poll()方法和poll(long var1, TimeUnit var3)方法

Poll()方法也是获取返回值,使用方法也跟take()一样。

而poll()方法和take()方法的区别就是,poll()方法不会阻塞的去等结果,而是如果调用poll()方法的时候没有结果可以获取就直接返回一个null,然后程序继续往下运行。

这时如果调用poll().get()可能会引发空指针异常java.lang.NullPointerException

例子:

依旧是一开始那三个任务,在循环中连续调用8次poll()方法,每次间隔1秒钟:

ExecutorService executor = Executors.newFixedThreadPool(5);
CompletionService completionService = new ExecutorCompletionService(executor);
completionService.submit(callable1);
completionService.submit(callable2);
completionService.submit(callable3);
System.out.println("两个任务提交完毕,开始获取结果 "+getStringDate());
for (int i = 0; i < 8; i++) {
              Future future = completionService.poll();
              if (future!=null){
              //如果future为空,会引发 NullPointerException
                  System.out.println(future.get() + getStringDate());
              }else {
                  System.out.println(future+" "+getStringDate());
              }
              Thread.sleep(1000);
          }          
System.out.println("获取结果完毕 "+getStringDate());

输出:

这里写图片描述

每次调用都是立马返回,毫不犹豫。所以没有结果的时候就返回空。

而poll(long var1, TimeUnit var3)方法就相当于给他强制设置了一个等待时间,你如果拿不到结果就等这么久,等这么久还拿不到再返回null。

把上面的循环改成这样:

for (int i = 0; i < 8; i++) {
    Future future = completionService.poll(1, TimeUnit.SECONDS);
    if (future!=null){
        System.out.println(future.get() + getStringDate());
    }else {
        System.out.println(future+" "+getStringDate());
    }
}

不在睡眠了,每次调用poll()方法个体1秒的等待时间。

这里写图片描述

这里第一次调用就等了1秒,然后在1秒内等到了call3的返回值,就返回call3的返回值。

第二次循环又等了一秒,一秒内没有获得结果,返回null。

Java并发编程-Executor框架之CompletionService

在前面我们已经学习过Executor框架之Callable和Future接口,我们知道利用list保存submit的callable任务所返回的Future对象。再在主线程中遍历这个list并调用Fu...
  • chenchaofuck1
  • chenchaofuck1
  • 2016年06月08日 21:02
  • 2262

多线程并发执行任务,取结果归集。终极总结:Future、FutureTask、CompletionService、CompletableFuture

http://www.cnblogs.com/dennyzhangdd/p/7010972.html 目录 1.Futrue 2.FutureTask 3....
  • zdy0_2004
  • zdy0_2004
  • 2017年06月20日 11:14
  • 829

Java并发编程(Callable、Future和CompletionService)

上篇博客主要讲解了一下java并发编程中的线程池,这篇呢来谈一下,java并发编程中的任务管理。这篇博客主要涉及到的Future、CompletionService、callable、runnable...
  • ZHOUCHAOQIANG
  • ZHOUCHAOQIANG
  • 2015年09月04日 18:11
  • 1456

java并发编程之CompletionService

应用场景当向Executor提交多个任务并且希望获得它们在完成之后的结果,如果用FutureTask,可以循环获取task,并调用get方法去获取task执行结果,但是如果task还未完成,获取结果的...
  • u010185262
  • u010185262
  • 2017年02月20日 17:58
  • 768

Java并发编程系列之二十八:CompletionService

CompletionService简介CompletionService与ExecutorService类似都可以用来执行线程池的任务,ExecutorService继承了Executor接口,而Co...
  • u011116672
  • u011116672
  • 2016年04月10日 17:01
  • 7245

聊聊高并发(四十二)解析java.util.concurrent各个组件(十八) 任务的批量执行和CompletionService

上一篇讲了ExecutorService关于任务的异步执行和状态控制的部分,这篇说说关于任务批量执行的部分。ExecutorSerivce中关于批量执行的...
  • ITer_ZC
  • ITer_ZC
  • 2015年07月20日 15:16
  • 2572

CompletionService 和ExecutorService的区别和用法

Java SE5的java.util.concurrent包中的执行器(Executor)将为你管理Thread对象,从而简化了并发编程。Executor在客户端和执行任务之间提供了一个间接层,Exe...
  • bigtree_3721
  • bigtree_3721
  • 2016年04月30日 20:55
  • 2301

java高并发之CompletionService优化多线程并发

1:还是上个需求 工作流策略的一个节点,需要查询13个第三方的数据,并返回查询结果变量,由于时间的要求,必须实现并行查询,而且要求13个查询全部完成才能返回工作流节点。 没看过上个帖子的可以看...
  • qq_15969757
  • qq_15969757
  • 2017年05月27日 16:09
  • 585

【Java并发】- 使用CompletionService异步收集任务结果

概述 在使用Executor或者直接使用ExecutorService的时候,通常会提交很多个任务同时还需要等待任务的返回结果,这个时候如何快速的回收任务的结果就成了问题。 而使用Completi...
  • LightOfMiracle
  • LightOfMiracle
  • 2017年07月03日 15:13
  • 532

java多线程-CompletionService

compeltionService 使用原因: 在并发任务时,如果需要获取并发任务执行后 返回结果,可以这样像ExecutorService提交Callable 任务,然后保留每个任务关联的Futu...
  • u010939285
  • u010939285
  • 2018年01月15日 19:42
  • 25
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:Java多线程-CompletionService
举报原因:
原因补充:

(最多只允许输入30个字)