任务中抛出异常

Java 9并发编程指南 目录

Java中有两种异常:

  • **受检异常:**这些异常必须指定在方法的throws子句中或者内部抓取。例如,IOException或者ClassNotFoundException。
  • **非受检异常:**这些异常不需要被指定或抓取,例如NumberFormatException。

在ForkJoinTask类的compute()方法中不能抛出任何受检异常,因为此方法的实现中不包含任何throws定义,需要加入必要的代码来处理受检异常。另一方面,此方法能够抛出(或者通过此方法内部使用的任何方法或对象抛出)非受检异常。ForkJoinTask和ForkJoinPool类的特性与所期待的不同。程序不会结束执行且不在控制台中看到任何异常信息,只是简单的接受好似异常不会被抛出。只有当调用初始任务的get()方法时,异常才会被抛出。当然,也可以使用ForkJoinTask类的一些方法去辨别任务是或否已经抛出异常,如果是,知道抛出什么类型的异常。在本节中,讲学习如何获取这些信息。

准备工作

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

实现过程

通过如下步骤实现范例:

  1. 创建名为Task的类,继承Integer类参数化的RecursiveTask类:

    public class Task extends RecursiveTask<Integer> {
    
  2. 定义名为array的私有int数组,用来模拟范例中将要处理的数据数组:

    	private int array[];
    
  3. 定义两个名为start和end的私有int属性,用来确定任务需要处理的数组元素:

    	private int start, end;
    
  4. 实现类构造函数,初始化这些属性:

    	public Task(int[] array, int start, int end) {
    		this.array = array;
    		this.start = start;
    		this.end = end;
    	}
    
  5. 实现任务的compute()方法,因为已经使用Integer类初始化RecursiveTask类,此方法返回Integer对象。首先,输出start和end属性值的信息到控制台:

    	@Override
    	protected Integer compute() {
    		System.out.printf("Task: Start from %d to %d\n",start,end);
    
  6. 如果任务需要处理的元素集合,由start和end属性确定的范围小于10,则判断数组中第四个元素(索引数是3)是否在集合中。如果是,抛出RuntimeException。然后,设置任务休眠一秒钟:

    		if (end-start<10) {
    			if ((3>start)&&(3<end)){
    				throw new RuntimeException("This task throws an"+"Exception: Task from "+start+" to "+end);
    			}
    			try {
    				TimeUnit.SECONDS.sleep(1);
    			} catch (InterruptedException e) {
    				e.printStackTrace();
    			}
    
  7. 否则(任务需要处理的元素集合数量大于等于10),将元素集合拆分成两块,创建两个Task对象来处理它们,并且在线程池中使用invokeAll()方法执行。然后输出这些任务的结果到控制台:

    		} else {
    			int mid=(end+start)/2;
    			Task task1=new Task(array,start,mid);
    			Task task2=new Task(array,mid,end);
    			invokeAll(task1, task2);
    			System.out.printf("Task: Result form %d to %d: %d\n",start,mid,task1.join());
    			System.out.printf("Task: Result form %d to %d: %d\n",mid,end,task2.join());
    		}	
    
  8. 输出指明任务结束的信息到控制台,包括start和end属性值:

    		System.out.printf("Task: End form %d to %d\n",start,end);
    
  9. 返回数值0作为任务结果:

    		return 0;
    	}
    
  10. 实现范例的主方法,创建一个包含main()方法的Main类:

    public class Main {
    	public static void main(String[] args) {
    
  11. 创建100个整型数组:

    		int array[] = new int[100];
    
  12. 创建Task对象处理这个数组:

    		Task task = new Task(array, 0, 100);
    
  13. 使用默认构造函数创建ForkJoinPool对象:

    		ForkJoinPool pool = new ForkJoinPool();
    
  14. 在线程池中使用execute()方法执行任务:

    		pool.execute(task);
    
  15. 使用shutdown()方法关闭ForkJoinPool类:

    		pool.shutdown();
    
  16. 使用awaitTermination()方法等待任务结束。因为需要足够长的时间等待任务结束,将数值1和TimeUnit.DAYS作为参数传给此方法:

    		try{
    			pool.awaitTermination(1, TimeUnit.DAYS);
    		} catch (InterruptedException e) {
    			e.printStackTrace();
    		}
    
  17. 使用isCompletedAbnormally()判断任务或者其子任务是否已经抛出异常。如果是,输出抛出的异常信息到控制台。使用ForkJoinTask类的getException()方法得到这个异常:

    		if (task.isCompletedAbnormally()) {
    			System.out.printf("Main: An exception has ocurred\n");
    			System.out.printf("Main: %s\n",task.getException());
    		}
    		System.out.printf("Main: Result: %d",task.join());
    	}
    

工作原理

本节中实现的Task类处理数字数组。任务判断其处理的数字集合数量是否大于等于10,如果是,拆分成两部分,并且创建两个新的Task对象分别处理它们。否则寻找数组中的第四个元素(索引数是3),如果此元素在任务处理的集合中,抛出RuntimeException异常。

当执行范例时抛出了异常,但程序不会停止。在Main类中,包含使用初始任务调用ForkJoinTask类的isCompletedAbnormally()方法。如果任务或其子任务已经抛出异常,方法返回true。还用到了同一个对象的getException()方法得到任务抛出的Exception对象。

当在任务中抛出非受检异常时,也会影响到父任务(发送到ForkJoinPool类的任务)以及父任务的父任务,以此类推。如果查看范例的全部输出,就会发现缺失一些任务结束的输出信息。如下所示这些任务的开始信息:

    Task: Start from 0 to 100
    Task: Start from 0 to 50
    Task: Start from 50 to 100
    Task: Start from 0 to 25
    Task: Start from 50 to 75
    Task: Start from 25 to 50
    Task: Start from 62 to 75

这些是抛出异常的任务及其父任务,都已经非正常结束。考虑到在开发过程中,如果不想要这种特性的话,使用能够抛出异常的ForkJoinPool和ForkJoinTask对象。

下图显示本范例在控制台输出的部分执行信息:

pics/05_03.jpg

扩展学习

本范例中,用到join()方法等待任务结束,且得到任务结果。也可以使用如下两种形式的get()方法达到此目的:

  • get():如果ForkJoinTask已经结束执行,此方法返回compute()方法返回的值,或者等待直到任务结束。
  • get(long timeout, TimeUnit unit):在这个方法中,如果任务的结果无效,则等待指定的时间,如果已过指定时间且结果依然无效,此方法返回null值。TimeUnit是一个枚举类型的类,包含如下常量:DAYS、HOURS、MICROSECONDS、MILLISECONDS、MINUTES、NANOSECONDS、和SECONDS。

get()和join()方法有两个主要的不同点:

  • join()方法不能被中断,如果中断调用join()方法的线程,此方法抛出InterruptedException异常。
  • 如果任务抛出任何非受检异常,get()方法将返回ExecutionException异常,然而join()方法将返回RuntimeException异常。

如果本范例中使用ForkJoinTask类的completeExceptionally()方法代替抛出异常,能够得到相同的结果。代码如下所示:

	Exception e=new Exception("This task throws an Exception: "+"Task from "+start+" to "+end);
	completeExceptionally(e);

更多关注

  • 本章“创建fork/join池”小节
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值