通常情况,当使用执行器运行并发任务时,会发送Runnable或Callable任务到执行器并且获得Future对象来控制方法。会出现需要在一个对象中发送任务到执行器,且在另一个对象中处理结果的局面。Java并发API提供的CompletionService类用来应对这种状况的发生。
CompletionService类提供发送任务到执行器的方法,以及为已经完成执行的下一个任务获得Future对象。此类内部使用Executor对象执行任务,这钟特性的优点是共享CompletionService对象且发送任务到执行器以便其他任务能够处理结果。不足之处则是第二个对象只能为已经完成执行的任务获得Future对象,所以这些Future对象只能用来得到任务结果。
本节中,学习如何使用CompletionService类将执行器中加载任务过程与任务结果的处理分开。
准备工作
本范例通过Eclipse开发工具实现。如果使用诸如NetBeans的开发工具,打开并创建一个新的Java项目。
实现过程
通过如下步骤完成范例:
-
创建名为ReportGenerator的类,实现String类参数化的Callable接口:
public class ReportGenerator implements Callable<String>{
-
定义名为sender和title的两个私有String属性,用来定义报告内容:
private final String sender; private final String title;
-
实现类构造函数,初始化两个属性:
public ReportGenerator(String sender, String title) { this.sender = sender; this.title = title; }
-
实现call()方法。首先,设置线程随机休眠一段时间:
@Override public String call() throws Exception { try{ Long duration = (long)(Math.random() * 10); System.out.printf("%s_%s : ReportGenerator : Generating a report during %d seconds\n", this.sender, this.title, duration); TimeUnit.SECONDS.sleep(duration); } catch(InterruptedException e){ e.printStackTrace(); }
-
然后用sender和title属性内容生成字符串的报告内容,并返回内容:
String ret = sender + " : " + title; return ret; }
-
创建名为ReportRequest的类,实现Runnable接口,用来模拟一些报告请求:
public class ReportRequest implements Runnable{
-
定义名为name的私有String属性,存储ReportRequest的名称:
private final String name;
-
定义名为service的私有CompletionService属性,CompletionService接口是String类参数化的接口:
private final CompletionService<String> service;
-
实现类构造函数,初始化两个属性:
public ReportRequest(String name, CompletionService<String> service) { this.name = name; this.service = service; }
-
实现run()方法,创建三个ReportGenerator对象,使用submit()方法发送到CompletionService对象:
@Override public void run() { ReportGenerator reportGenerator = new ReportGenerator(name, "Report"); service.submit(reportGenerator); }
-
创建名为ReportProcessor的类,此类将得到ReportGenerator任务的结果,指定其实现Runnable接口:
public class ReportProcessor implements Runnable{
-
定义名为service的私有CompletionService属性,因为CompletionService接口是参数化接口,所以使用String类作为CompletionService接口的参数:
private final CompletionService<String> service;
-
定义名为end的私有Boolean属性,添加volatile 关键字确保所有线程拥有此属性实际值的使用权:
private volatile boolean end;
-
定义类构造函数,初始化这两个属性:
public ReportProcessor(CompletionService<String> service) { this.service = service; end = false; }
-
实现run()方法,当end属性是false时,调用CompletionService接口的poll()方法,获得下一个任务的Future对象,此任务被已经结束的完成服务执行:
@Override public void run() { while (!end){ try { Future<String> result = service.poll(20, TimeUnit.SECONDS);
-
然后,使用Future对象的get()方法得到任务结果,并输出结果到控制台:
if(result != null) { String report = result.get(); System.out.printf("ReportReceiver : Report Received : %s\n", report); } } catch (InterruptedException | ExecutionException e) { // TODO Auto-generated catch block e.printStackTrace(); } } System.out.printf("ReportSender : End\n"); }
-
实现stopProcessing()方法来改变end属性值:
public void stopProcessing(){ this.end = true; }
-
实现范例的主方法,创建一个包含main()方法的Main类:
public class Main { public static void main(String[] args) {
-
使用Executors类的newCachedThreadPool()方法创建ThreadPoolExecutor:
ExecutorService executor = Executors.newCachedThreadPool();
-
使用执行器创建CompletionService,此执行器作为前面的构造函数的参数被创建:
CompletionService<String> service = new ExecutorCompletionService<>(executor);
-
创建两个ReportRequest对象,以及执行它们的线程:
ReportRequest faceRequest = new ReportRequest("Face", service); ReportRequest onlineRequest = new ReportRequest("Online", service); Thread faceThread = new Thread(faceRequest); Thread onlineThread = new Thread(onlineRequest);
-
创建ReportProcessor对象,以及执行它的线程:
ReportProcessor processor = new ReportProcessor(service); Thread senderThread = new Thread(processor);
-
启动三个线程:
System.out.printf("Main : Starting the Threads\n"); faceThread.start(); onlineThread.start(); senderThread.start();
-
等待ReportRequest线程的结束:
try { System.out.printf("Main : Waiting for the report generators.\n"); faceThread.join(); onlineThread.join(); } catch (InterruptedException e) { e.printStackTrace(); }
-
使用shutdown()方法结束执行器,然后使用awaitTermination()方法等待任务的结束:
System.out.printf("Main : Shutting down the executor.\n"); executor.shutdown(); try { executor.awaitTermination(1, TimeUnit.DAYS); } catch (InterruptedException e) { e.printStackTrace(); }
-
结束ReportSender对象的执行,设置end属性值为true:
processor.stopProcessing(); System.out.println("Main : Ends");
工作原理
本范例主类中,使用Executors类的newCachedThreadPool()方法创建ThreadPoolExecutor。然后,用到Executor对象初始化CompletionService对象,因为完成服务使用执行器运行其任务。为了使用完成服务执行任务,用到ReportRequest类中的submit()方法。
当完成服务结束其执行时,其中一个任务被执行,服务存储Future对象用来在队列中控制执行。poll()方法进入队列判断是否有任务已经完成执行,如果有,则返回队列的第一个元素,即任务已经完成执行的Future对象。当poll()方法返回Future对象时,此对象从队列中删除自己。这种情况下,假定队列包含的完成任务的结果为空,则传递两个属性到此方法,指明想要等待任务完成的时间。
一旦CompletionService对象被创建,则创建了两个ReportRequest对象用来执行一个ReportGenerator任务,使用之前创建的CompletionService对象执行另一个ReportGenerator任务,此对象作为参数传递到ReportGenerator对象的构造函数中。
扩展学习
CompletionService类能够执行Callable或者Runnable任务,本范例中用到了Callable,但是也可以发送Runnable对象。由于Runnable对象不产生结果,所以CompletionService类设计也没有考虑到此情形。
这个类提供了两个方法获得完成任务的Future对象,如下所示:
- poll():不带参数的poll()方法判断队列中是否有Future对象。如果队列为空,立即返回空,否则,返回第一个元素并从队列中删除它。
- take():此方法不带参数,判断队列中是否有Future对象。如果队列为空,则阻塞线程直到队列具有一个元素,如果队列包含元素,此方法返回第一个元素,并且从队列中删除它。
本范例中,超时使用poll()方法来控制结束ReportProcessor任务的执行。
更多关注
本章“执行器中运行返回结果的任务“小节。