关闭

Java多线程-CompletionService

标签: java多线程线程池
130人阅读 评论(0) 收藏 举报
分类:

原文地址 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。

0
0

查看评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
    个人资料
    • 访问:45511次
    • 积分:1159
    • 等级:
    • 排名:千里之外
    • 原创:75篇
    • 转载:1篇
    • 译文:0篇
    • 评论:52条
    博客专栏
    最新评论