概述
在使用Executor或者直接使用ExecutorService的时候,通常会提交很多个任务同时还需要等待任务的返回结果,这个时候如何快速的回收任务的结果就成了问题。
而使用CompletionService可以生产新的异步任务与使用已完成任务的结果分离开来。比如前端页面的图片这种IO的任务,我们并不关心图片的顺序,只是希望能尽快的将图片展示出来。这个时候如果只是顺序的去取图片结果的话,那么很可能顺序排在后面的图片其实很小但是却很晚才能显示出来。
使用
由一个简单的发送消息的Demo来演示
顺序的取任务结果
使用一个List去记录submit返回的future,然后顺序的取回结果
一共执行10个任务messageId从0~10
messageId % 4 == 0的任务的执行时间会比其他任务长很多
public class SendMessageDemo {
//线程池中核心线程数
private final static int THREAD_POOL_SIZE = 3;
private static final int MAX_THREAD_POOL_SIZE = 5;
public static void main(String[] args){
test1();
}
public static void test1() {
//任务阻塞队列
BlockingQueue<Runnable> workQueue = new ArrayBlockingQueue<>(10);
ThreadPoolExecutor excutor = new ThreadPoolExecutor(THREAD_POOL_SIZE, MAX_THREAD_POOL_SIZE, 0, TimeUnit.MILLISECONDS, workQueue);
List<Future<Integer>> list = new ArrayList<>();
for(int i = 0; i < 10; i++){
//将任务提交到阻塞队列
Future<Integer> future = excutor.submit(new MessageTask(i));
list.add(future);
}
//不再接收新的任务
excutor.shutdown();
final long start = System.nanoTime();
while (!list.isEmpty()) {
for(Iterator<Future<Integer>> it = list.iterator(); it.hasNext();) {
Integer r = null;
Future<Integer> f = it.next();
try {
r = f.get();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
it.remove();
System.out.println("result:" + r + "----after " + (System.nanoTime() - start) / 1000 / 1000 + " miliseconds");
}
}
}
}
//Callable任务
class MessageTask implements Callable<Integer>{
private int messageId;
public MessageTask(int messageId){
this.messageId = messageId;
}
@Override
public Integer call() {
//System.out.println("send message start : " + messageId);
try {
Thread.sleep(messageId % 4 == 0 ? 3000 : 100);
} catch (InterruptedException e) {
e.printStackTrace();
}
//System.out.println("send message end : " + messageId);
return messageId;
}
public int getMessageId() {
return messageId;
}
}
输出:
result:0----after 2995 miliseconds
result:1----after 2996 miliseconds
result:2----after 2996 miliseconds
result:3----after 2996 miliseconds
result:4----after 3108 miliseconds
result:5----after 3108 miliseconds
result:6----after 3109 miliseconds
result:7----after 3109 miliseconds
result:8----after 3545 miliseconds
result:9----after 3546 miliseconds
可以看到所有的任务都是在3秒以后才收集到结果。
这是因为Future.get()方法会去阻塞到任务返回为止。所以导致其他已经完成的任务虽然已经返回但是没能及时的收集到结果。
轮询的方式
可以将上面的test1()方法的地方稍微改一下public static void test2() {
//任务阻塞队列
BlockingQueue<Runnable> workQueue = new ArrayBlockingQueue<>(10);
ThreadPoolExecutor excutor = new ThreadPoolExecutor(THREAD_POOL_SIZE, MAX_THREAD_POOL_SIZE, 0, TimeUnit.MILLISECONDS, workQueue);
List<Future<Integer>> list = new ArrayList<>();
for(int i = 0; i < 10; i++){
//将任务提交到阻塞队列
Future<Integer> future = excutor.submit(new MessageTask(i));
list.add(future);
}
//不再接收新的任务
excutor.shutdown();
//异步收集结果
final long start = System.nanoTime();
while(!list.isEmpty()){
for(Iterator<Future<Integer>> it = list.iterator(); it.hasNext();) {
Integer r = null;
Future<Integer> f = it.next();
try {
//当前任务还没完成的话直接返回
r = f.get(0, TimeUnit.SECONDS);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
} catch (TimeoutException e) {
//e.printStackTrace();
}
//收集到结果则使用
if (null != r) {
it.remove();
System.out.println("result:" + r + "----after " + (System.nanoTime() - start) / 1000 / 1000 + " miliseconds");
break;
}
}
}
}
输出:
result:2----after 100 miliseconds
result:1----after 101 miliseconds
result:3----after 204 miliseconds
result:5----after 313 miliseconds
result:6----after 423 miliseconds
result:7----after 532 miliseconds
result:0----after 2993 miliseconds
result:4----after 3105 miliseconds
result:9----after 3105 miliseconds
result:8----after 3543 miliseconds
可以看先完成的任务率先回收了结果并使用。
但是这种轮询的方式从实现方式上似乎还是有点点不太优雅。
使用CompletionService
下面的test3中不使用轮询使用CompletionService的方式(实际上是阻塞队列的方式,一旦有任务完成则将结果放入阻塞队列)。
//CompletionService收集结果
public static void test3() {
BlockingQueue<Runnable> workQueue = new ArrayBlockingQueue<>(10);
ThreadPoolExecutor excutor = new ThreadPoolExecutor(THREAD_POOL_SIZE, MAX_THREAD_POOL_SIZE, 0, TimeUnit.MILLISECONDS, workQueue);
CompletionService<Integer> completionService = new ExecutorCompletionService<>(excutor);
for(int i = 0; i < 10; i++){
//将任务提交到阻塞队列
completionService.submit(new MessageTask(i));
}
//不再接收新的任务
excutor.shutdown();
final long start = System.nanoTime();
for(int i = 0; i < 10; i++){
try {
//如果有任务完成那么就从take中返回
Future<Integer> f = completionService.take();
System.out.println("result:" + f.get() + "----after " + (System.nanoTime() - start) / 1000 / 1000 + " miliseconds");
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
}
}
输出:
result:1----after 108 miliseconds
result:2----after 109 miliseconds
result:3----after 216 miliseconds
result:5----after 325 miliseconds
result:6----after 435 miliseconds
result:7----after 541 miliseconds
result:0----after 3010 miliseconds
result:9----after 3122 miliseconds
result:4----after 3122 miliseconds
result:8----after 3544 miliseconds
和test2中差不多,但是不是使用的轮询的方式。
注意:CompletionService并不能影响线程池执行任务的效率,只是对于结果的收集更加的合理。想要提高实际的执行效率的话此路不通。。对于异步处理来说很有用。