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

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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获取结果的使用方法是这样的:

1
2
3
4
5
6
7
8
9
10
11
12
//声明一个线程池
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是一个接口:

1
2
3
4
5
6
7
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的构造方法有两个:

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

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

下面是一个简单例子:

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//新建一个线程池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()方法那里等待一个结果

1
2
3
4
5
6
7
8
9
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秒钟:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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。

把上面的循环改成这样:

1
2
3
4
5
6
7
8
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。