Java多线程-CompletionService

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,723评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,485评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,998评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,323评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,355评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,079评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,389评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,019评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,519评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,971评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,100评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,738评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,293评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,289评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,517评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,547评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,834评论 2 345

推荐阅读更多精彩内容