[java] view plain copy
- public interface CompletionService<V> {
- Future<V> submit(Callable<V> task);
- Future<V> submit(Runnable task, V result);
- /**
- * Retrieves and removes the Future representing the next
- * completed task, waiting if none are yet present.
- *
- * @return the Future representing the next completed task
- * @throws InterruptedException if interrupted while waiting
- * 阻塞/
- Future<V> take() throws InterruptedException;
- /**
- * Retrieves and removes the Future representing the next
- * completed task or <tt>null</tt> if none are present.
- *
- * @return the Future representing the next completed task, or
- * <tt>null</tt> if none are present
- * 非阻塞/
- Future<V> poll();
- }
CompletionService 也不是到处都能用,它不适合处理任务数量有限但个数不可知的场景。例如,要统计某个文件夹中的文件个数,在遍历子文件夹的时候也会“递归地”提交新的任务,但最后到底提交了多少,以及在什么时候提交完了所有任务,都是未知数,无论 CompletionService 还是线程池都无法进行判断。这种情况只能直接用线程池来处理。
CompletionService 接口的实例可以充当生产者和消费者的中间处理引擎,从而达到将提交任务和处理结果的代码进行解耦的目的。生产者调用submit 方法提交任务,而消费者调用 poll(非阻塞)或 take(阻塞)方法获取下一个结果:这一特征看起来和阻塞队列(BlockingQueue)类似,两者的区别在于 CompletionService 要负责任务的处理,而阻塞队列则不会。
在 JDK 中,该接口只有一个实现类 ExecutorCompletionService,该类使用创建时提供的 Executor 对象(通常是线程池)来执行任务,然后将结果放入一个阻塞队列中。
Example1:
1. 背景
在Java5的多线程中,可以使用Callable接口来实现具有返回值的线程。使用线程池的submit方法提交Callable任务,利用submit方法返回的Future存根,调用此存根的get方法来获取整个线程池中所有任务的运行结果。
方法一:如果是自己写代码,应该是自己维护一个Collection保存submit方法返回的Future存根,然后在主线程中遍历这个Collection并调用Future存根的get()方法取到线程的返回值。
方法二:使用CompletionService类,它整合了Executor和BlockingQueue的功能。你可以将Callable任务提交给它去执行,然后使用类似于队列中的take方法获取线程的返回值。
2. 实现代码
[java] view plain copy
- package com.clzhang.sample.thread;
- import java.util.*;
- import java.util.concurrent.BlockingQueue;
- import java.util.concurrent.Callable;
- import java.util.concurrent.CompletionService;
- import java.util.concurrent.ExecutorCompletionService;
- import java.util.concurrent.ExecutorService;
- import java.util.concurrent.Executors;
- import java.util.concurrent.Future;
- import java.util.concurrent.LinkedBlockingQueue;
- public class ThreadPoolTest4 {
- // 具有返回值的测试线程
- class MyThread implements Callable<String> {
- private String name;
- public MyThread(String name) {
- this.name = name;
- }
- @Override
- public String call() {
- int sleepTime = new Random().nextInt(1000);
- try {
- Thread.sleep(sleepTime);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- // 返回给调用者的值
- String str = name + " sleep time:" + sleepTime;
- System.out.println(name + " finished...");
- return str;
- }
- }
- private final int POOL_SIZE = 5;
- private final int TOTAL_TASK = 20;
- // 方法一,自己写集合来实现获取线程池中任务的返回结果
- public void testByQueue() throws Exception {
- // 创建线程池
- ExecutorService pool = Executors.newFixedThreadPool(POOL_SIZE);
- BlockingQueue<Future<String>> queue = new LinkedBlockingQueue<Future<String>>();
- // 向里面扔任务
- for (int i = 0; i < TOTAL_TASK; i++) {
- Future<String> future = pool.submit(new MyThread("Thread" + i));
- queue.add(future);
- }
- // 检查线程池任务执行结果
- for (int i = 0; i < TOTAL_TASK; i++) {
- System.out.println("method1:" + queue.take().get());
- }
- // 关闭线程池
- pool.shutdown();
- }
- // 方法二,通过CompletionService来实现获取线程池中任务的返回结果
- public void testByCompetion() throws Exception {
- // 创建线程池
- ExecutorService pool = Executors.newFixedThreadPool(POOL_SIZE);
- CompletionService<String> cService = new ExecutorCompletionService<String>(pool);
- // 向里面扔任务
- for (int i = 0; i < TOTAL_TASK; i++) {
- cService.submit(new MyThread("Thread" + i));
- }
- // 检查线程池任务执行结果
- for (int i = 0; i < TOTAL_TASK; i++) {
- Future<String> future = cService.take();
- System.out.println("method2:" + future.get());
- }
- // 关闭线程池
- pool.shutdown();
- }
- public static void main(String[] args) throws Exception {
- ThreadPoolTest4 t = new ThreadPoolTest4();
- t.testByQueue();
- t.testByCompetion();
- }
- }
部分输出:
...
Thread4 finished...
method1:Thread4 sleep time:833
method1:Thread5 sleep time:158
Thread6 finished...
method1:Thread6 sleep time:826
method1:Thread7 sleep time:185
Thread9 finished...
Thread8 finished...
method1:Thread8 sleep time:929
method1:Thread9 sleep time:575
...
Thread11 finished...
method2:Thread11 sleep time:952
Thread18 finished...
method2:Thread18 sleep time:793
Thread19 finished...
method2:Thread19 sleep time:763
Thread16 finished...
method2:Thread16 sleep time:990
...
3. 总结
使用方法一,自己创建一个集合来保存Future存根并循环调用其返回结果的时候,主线程并不能保证首先获得的是最先完成任务的线程返回值。它只是按加入线程池的顺序返回。因为take方法是阻塞方法,后面的任务完成了,前面的任务却没有完成,主程序就那样等待在那儿,只到前面的完成了,它才知道原来后面的也完成了。
使用方法二,使用CompletionService来维护处理线程不的返回结果时,主线程总是能够拿到最先完成的任务的返回值,而不管它们加入线程池的顺序。
Example2:
当向Executor提交批处理任务时,并且希望在它们完成后获得结果,如果用FutureTask,你可以循环获取task,并用future.get()去获取结果,但是如果这个task没有完成,你就得阻塞在这里,这个实效性不高,其实在很多场合,其实你拿第一个任务结果时,此时结果并没有生成并阻塞,其实在阻塞在第一个任务时,第二个task的任务已经早就完成了,显然这种情况用future task不合适的,效率也不高的,实例如下:
实例一:用一个非complete Service完成的批量任务
[java] view plain copy
- public class NonCompleteServiceTest {
- public static void main(String[] args) throws InterruptedException, ExecutionException {
- ExecutorService executorService = Executors.newFixedThreadPool(10);
- Future<String>[] futures = new FutureTask[10];
- /**
- * 产生一个随机数,模拟不同的任务的处理时间不同
- */
- for (int i = 0; i < 10; i++) {
- futures[i] = executorService.submit(new Callable<String>() {
- public String call(){
- int rnt = new Random().nextInt(5);
- try {
- Thread.sleep(rnt*1000);
- } catch (InterruptedException e) {
- // TODO Auto-generated catch block
- e.printStackTrace();
- }
- System.out.println("run rnt = "+rnt);
- return String.valueOf(rnt*1000);
- }
- });
- }
- /**
- * 获取结果时,如果任务没有完成,则阻塞,在顺序获取结果时,
- * 可能别的任务已经完成,显然效率不高
- */
- for (int i = 0; i < futures.length; i++) {
- System.out.println(futures[i].get());
- }
- executorService.shutdown();
- }
- }
CompletionService:
JDK:
public interface CompletionService<V>
将生产新的异步任务与使用已完成任务的结果分离开来的服务。生产者 submit 执行的任务。使用者 take 已完成的任务,并按照完成这些任务的顺序处理它们的结果。例如,CompletionService 可以用来管理异步 IO ,执行读操作的任务作为程序或系统的一部分提交,然后,当完成读操作时,会在程序的不同部分执行其他操作,执行操作的顺序可能与所请求的顺序不同。
[java] view plain copy
- public class CompleteServiceTest {
- public static void main(String[] args) throws InterruptedException, ExecutionException {
- ExecutorService executorService = Executors.newFixedThreadPool(10);
- CompletionService<String> completionService = new ExecutorCompletionService<String>(executorService);
- /**
- * 产生一个随机数,模拟不同的任务的处理时间不同
- */
- for (int i = 0; i < 10; i++) {
- completionService.submit(new Callable<String>() {
- public String call(){
- int rnt = new Random().nextInt(5);
- try {
- Thread.sleep(rnt*1000);
- } catch (InterruptedException e) {
- // TODO Auto-generated catch block
- e.printStackTrace();
- }
- System.out.println("run rnt = "+rnt);
- return String.valueOf(rnt*1000);
- }
- });
- }
- /**
- * 获取结果时,总是先拿到队列上已经存在的对象,这样不用依次等待结果
- * 显然效率更高
- */
- for (int i = 0; i < 10; i++) {
- Future<String> future = completionService.take();
- System.out.println(future.get());
- }
- executorService.shutdown();
- }
- }
实例来源:http://tomyz0223.iteye.com/blog/1040300
JDK:http://www.cjsdn.net/Doc/JDK50/java/util/concurrent/CompletionService.html