使用CompletionService来维护处理线程的返回结果

本文详细介绍了CompletionService接口在Java并发编程中的应用,包括如何使用CompletionService处理异步任务的结果,对比了使用Future和CompletionService的不同之处,并提供了具体的实现代码示例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

[java] view plain copy

  1. public interface CompletionService<V> {  
  2.       
  3.     Future<V> submit(Callable<V> task);  
  4.       
  5.     Future<V> submit(Runnable task, V result);  
  6.   
  7.     /**  
  8.      * Retrieves and removes the Future representing the next  
  9.      * completed task, waiting if none are yet present.  
  10.      *  
  11.      * @return the Future representing the next completed task  
  12.      * @throws InterruptedException if interrupted while waiting  
  13.      * 阻塞/  
  14.     Future<V> take() throws InterruptedException;  
  15.   
  16.   
  17.     /**  
  18.      * Retrieves and removes the Future representing the next  
  19.      * completed task or <tt>null</tt> if none are present.  
  20.      *  
  21.      * @return the Future representing the next completed task, or  
  22.      *         <tt>null</tt> if none are present  
  23.      * 非阻塞/  
  24.     Future<V> poll();  
  25. }  

 

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

  1. package com.clzhang.sample.thread;  
  2.   
  3. import java.util.*;  
  4. import java.util.concurrent.BlockingQueue;  
  5. import java.util.concurrent.Callable;  
  6. import java.util.concurrent.CompletionService;  
  7. import java.util.concurrent.ExecutorCompletionService;  
  8. import java.util.concurrent.ExecutorService;  
  9. import java.util.concurrent.Executors;  
  10. import java.util.concurrent.Future;  
  11. import java.util.concurrent.LinkedBlockingQueue;  
  12.   
  13. public class ThreadPoolTest4 {  
  14.     // 具有返回值的测试线程  
  15.     class MyThread implements Callable<String> {  
  16.         private String name;  
  17.         public MyThread(String name) {  
  18.             this.name = name;  
  19.         }  
  20.   
  21.         @Override  
  22.         public String call() {  
  23.             int sleepTime = new Random().nextInt(1000);  
  24.             try {  
  25.                 Thread.sleep(sleepTime);  
  26.             } catch (InterruptedException e) {  
  27.                 e.printStackTrace();  
  28.             }  
  29.   
  30.             // 返回给调用者的值  
  31.             String str = name + " sleep time:" + sleepTime;  
  32.             System.out.println(name + " finished...");  
  33.   
  34.             return str;  
  35.         }  
  36.     }  
  37.   
  38.     private final int POOL_SIZE = 5;  
  39.     private final int TOTAL_TASK = 20;  
  40.   
  41.     // 方法一,自己写集合来实现获取线程池中任务的返回结果  
  42.     public void testByQueue() throws Exception {  
  43.         // 创建线程池  
  44.         ExecutorService pool = Executors.newFixedThreadPool(POOL_SIZE);  
  45.         BlockingQueue<Future<String>> queue = new LinkedBlockingQueue<Future<String>>();  
  46.   
  47.         // 向里面扔任务  
  48.         for (int i = 0; i < TOTAL_TASK; i++) {  
  49.             Future<String> future = pool.submit(new MyThread("Thread" + i));  
  50.             queue.add(future);  
  51.         }  
  52.   
  53.         // 检查线程池任务执行结果  
  54.         for (int i = 0; i < TOTAL_TASK; i++) {  
  55.             System.out.println("method1:" + queue.take().get());  
  56.         }  
  57.   
  58.         // 关闭线程池  
  59.         pool.shutdown();  
  60.     }  
  61.   
  62.     // 方法二,通过CompletionService来实现获取线程池中任务的返回结果  
  63.     public void testByCompetion() throws Exception {  
  64.         // 创建线程池  
  65.         ExecutorService pool = Executors.newFixedThreadPool(POOL_SIZE);  
  66.         CompletionService<String> cService = new ExecutorCompletionService<String>(pool);  
  67.   
  68.         // 向里面扔任务  
  69.         for (int i = 0; i < TOTAL_TASK; i++) {  
  70.             cService.submit(new MyThread("Thread" + i));  
  71.         }  
  72.   
  73.         // 检查线程池任务执行结果  
  74.         for (int i = 0; i < TOTAL_TASK; i++) {  
  75.             Future<String> future = cService.take();  
  76.             System.out.println("method2:" + future.get());  
  77.         }  
  78.   
  79.         // 关闭线程池  
  80.         pool.shutdown();  
  81.     }  
  82.   
  83.     public static void main(String[] args) throws Exception {  
  84.         ThreadPoolTest4 t = new ThreadPoolTest4();  
  85.         t.testByQueue();  
  86.         t.testByCompetion();  
  87.     }  
  88. }  

 

 

部分输出:

...

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

  1. public class NonCompleteServiceTest {  
  2.       
  3.     public static void main(String[] args) throws InterruptedException, ExecutionException {  
  4.         ExecutorService executorService = Executors.newFixedThreadPool(10);  
  5.         Future<String>[] futures = new FutureTask[10];          
  6.         /** 
  7.          * 产生一个随机数,模拟不同的任务的处理时间不同 
  8.          */  
  9.         for (int i = 0; i < 10; i++) {  
  10.             futures[i] = executorService.submit(new Callable<String>() {  
  11.                 public String call(){  
  12.                     int rnt = new Random().nextInt(5);  
  13.                       
  14.                     try {  
  15.                         Thread.sleep(rnt*1000);  
  16.                     } catch (InterruptedException e) {  
  17.                         // TODO Auto-generated catch block  
  18.                         e.printStackTrace();  
  19.                     }  
  20.                     System.out.println("run rnt = "+rnt);  
  21.                     return String.valueOf(rnt*1000);  
  22.                 }  
  23.             });  
  24.         }  
  25.           
  26.         /** 
  27.          * 获取结果时,如果任务没有完成,则阻塞,在顺序获取结果时, 
  28.          * 可能别的任务已经完成,显然效率不高 
  29.          */  
  30.         for (int i = 0; i < futures.length; i++) {  
  31.             System.out.println(futures[i].get());  
  32.         }  
  33.         executorService.shutdown();  
  34.     }  
  35.   
  36. }  

 

CompletionService:

 

JDK:

 

public interface CompletionService<V>

将生产新的异步任务与使用已完成任务的结果分离开来的服务。生产者 submit 执行的任务。使用者 take 已完成的任务,并按照完成这些任务的顺序处理它们的结果。例如,CompletionService 可以用来管理异步 IO ,执行读操作的任务作为程序或系统的一部分提交,然后,当完成读操作时,会在程序的不同部分执行其他操作,执行操作的顺序可能与所请求的顺序不同。

 

 

 

[java] view plain copy

  1. public class CompleteServiceTest {  
  2. public static void main(String[] args) throws InterruptedException, ExecutionException {  
  3.     ExecutorService executorService = Executors.newFixedThreadPool(10);  
  4.       
  5.       
  6.     CompletionService<String> completionService = new ExecutorCompletionService<String>(executorService);  
  7.       
  8.     /** 
  9.      * 产生一个随机数,模拟不同的任务的处理时间不同 
  10.      */  
  11.     for (int i = 0; i < 10; i++) {  
  12.         completionService.submit(new Callable<String>() {  
  13.             public String call(){  
  14.                 int rnt = new Random().nextInt(5);  
  15.                   
  16.                 try {  
  17.                     Thread.sleep(rnt*1000);  
  18.                 } catch (InterruptedException e) {  
  19.                     // TODO Auto-generated catch block  
  20.                     e.printStackTrace();  
  21.                 }  
  22.                 System.out.println("run rnt = "+rnt);  
  23.                 return String.valueOf(rnt*1000);  
  24.             }  
  25.         });  
  26.     }  
  27.       
  28.     /** 
  29.      * 获取结果时,总是先拿到队列上已经存在的对象,这样不用依次等待结果 
  30.      * 显然效率更高 
  31.      */  
  32.     for (int i = 0; i < 10; i++) {  
  33.         Future<String> future = completionService.take();  
  34.         System.out.println(future.get());  
  35.     }  
  36.     executorService.shutdown();  
  37. }  
  38. }  

 实例来源:http://tomyz0223.iteye.com/blog/1040300

JDK:http://www.cjsdn.net/Doc/JDK50/java/util/concurrent/CompletionService.html

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值