执行器中分离任务加载和结果处理

Java 9并发编程指南 目录

执行器中分离任务加载和结果处理

通常情况,当使用执行器运行并发任务时,会发送Runnable或Callable任务到执行器并且获得Future对象来控制方法。会出现需要在一个对象中发送任务到执行器,且在另一个对象中处理结果的局面。Java并发API提供的CompletionService类用来应对这种状况的发生。

CompletionService类提供发送任务到执行器的方法,以及为已经完成执行的下一个任务获得Future对象。此类内部使用Executor对象执行任务,这钟特性的优点是共享CompletionService对象且发送任务到执行器以便其他任务能够处理结果。不足之处则是第二个对象只能为已经完成执行的任务获得Future对象,所以这些Future对象只能用来得到任务结果。

本节中,学习如何使用CompletionService类将执行器中加载任务过程与任务结果的处理分开。

准备工作

本范例通过Eclipse开发工具实现。如果使用诸如NetBeans的开发工具,打开并创建一个新的Java项目。

实现过程

通过如下步骤完成范例:

  1. 创建名为ReportGenerator的类,实现String类参数化的Callable接口:

    public class ReportGenerator implements Callable<String>{
    
  2. 定义名为sender和title的两个私有String属性,用来定义报告内容:

    	private final String sender;
    	private final String title;
    
  3. 实现类构造函数,初始化两个属性:

    	public ReportGenerator(String sender, String title) {
    		this.sender = sender;
    		this.title = title;
    	}
    
  4. 实现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();
    		}
    
  5. 然后用sender和title属性内容生成字符串的报告内容,并返回内容:

    		String ret = sender + " :  " + title;
    		return ret;
    	}
    
  6. 创建名为ReportRequest的类,实现Runnable接口,用来模拟一些报告请求:

    public class ReportRequest implements Runnable{
    
  7. 定义名为name的私有String属性,存储ReportRequest的名称:

    	private final String name;
    
  8. 定义名为service的私有CompletionService属性,CompletionService接口是String类参数化的接口:

    	private final CompletionService<String> service;
    
  9. 实现类构造函数,初始化两个属性:

    	public ReportRequest(String name, CompletionService<String> service) {
    		this.name = name;
    		this.service = service;
    	}
    
  10. 实现run()方法,创建三个ReportGenerator对象,使用submit()方法发送到CompletionService对象:

    	@Override
    	public void run() {
    		ReportGenerator reportGenerator = new ReportGenerator(name, "Report");
    		service.submit(reportGenerator);
    	}
    
  11. 创建名为ReportProcessor的类,此类将得到ReportGenerator任务的结果,指定其实现Runnable接口:

    public class ReportProcessor implements Runnable{
    
  12. 定义名为service的私有CompletionService属性,因为CompletionService接口是参数化接口,所以使用String类作为CompletionService接口的参数:

    	private final CompletionService<String> service;
    
  13. 定义名为end的私有Boolean属性,添加volatile 关键字确保所有线程拥有此属性实际值的使用权:

    	private volatile boolean end;
    
  14. 定义类构造函数,初始化这两个属性:

    	public ReportProcessor(CompletionService<String> service) {
    		this.service = service;
    		end = false;
    	}
    
  15. 实现run()方法,当end属性是false时,调用CompletionService接口的poll()方法,获得下一个任务的Future对象,此任务被已经结束的完成服务执行:

    	@Override
    	public void run() {
    		while (!end){
    			try {
    				Future<String> result = service.poll(20, TimeUnit.SECONDS);
    
  16. 然后,使用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");
    	}
    
  17. 实现stopProcessing()方法来改变end属性值:

    	public void stopProcessing(){
    		this.end = true;
    	}
    
  18. 实现范例的主方法,创建一个包含main()方法的Main类:

    public class Main {
    	public static void main(String[] args) {
    
  19. 使用Executors类的newCachedThreadPool()方法创建ThreadPoolExecutor:

    		ExecutorService executor = Executors.newCachedThreadPool();
    
  20. 使用执行器创建CompletionService,此执行器作为前面的构造函数的参数被创建:

    		CompletionService<String> service = new ExecutorCompletionService<>(executor);
    
  21. 创建两个ReportRequest对象,以及执行它们的线程:

    		ReportRequest faceRequest = new ReportRequest("Face", service);
    		ReportRequest onlineRequest = new ReportRequest("Online", service);
    		
    		Thread faceThread = new Thread(faceRequest);
    		Thread onlineThread = new Thread(onlineRequest);
    
  22. 创建ReportProcessor对象,以及执行它的线程:

    		ReportProcessor processor = new ReportProcessor(service);
    		Thread senderThread = new Thread(processor);
    
  23. 启动三个线程:

    		System.out.printf("Main : Starting the Threads\n");
    		faceThread.start();
    		onlineThread.start();
    		senderThread.start();
    
  24. 等待ReportRequest线程的结束:

    		try {
    			System.out.printf("Main : Waiting for the report generators.\n");
    			faceThread.join();
    			onlineThread.join();
    		} catch (InterruptedException e) {
    			e.printStackTrace();
    		}
    
  25. 使用shutdown()方法结束执行器,然后使用awaitTermination()方法等待任务的结束:

    		System.out.printf("Main : Shutting down the executor.\n");
    		executor.shutdown();
    		try {
    			executor.awaitTermination(1, TimeUnit.DAYS);
    		} catch (InterruptedException e) {
    			e.printStackTrace();
    		}
    
  26. 结束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任务的执行。

更多关注

本章“执行器中运行返回结果的任务“小节。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值