Fork/Join框架(五)在任務中丟擲異常
宣告:本文是《 Java 7 Concurrency Cookbook 》的第五章,作者: Javier Fernández González 譯者:許巧輝 校對:方騰飛
在任務中丟擲異常
在Java中有兩種異常:
- 已檢查異常(Checked exceptions):這些異常必須在一個方法的throws從句中指定或在內部捕捉它們。比如:IOException或ClassNotFoundException。
- 未檢查異常(Unchecked exceptions):這些異常不必指定或捕捉。比如:NumberFormatException。
在ForkJoinTask類的compute()方法中,你不能丟擲任何已檢查異常,因為在這個方法的實現中,它沒有包含任何丟擲(異常)宣告。你必須包含必要的程式碼來處理異常。但是,你可以丟擲(或者它可以被任何方法或使用內部方法的物件丟擲)一個未檢查異常。ForkJoinTask和ForkJoinPool類的行為與你可能的期望不同。程式不會結束執行,並且你將不會在控制檯看到任何關於異常的資訊。它只是被吞沒,好像它沒丟擲(異常)。你可以使用ForkJoinTask類的一些方法,得知一個任務是否丟擲異常及其異常種類。在這個指南中,你將學習如何獲取這些資訊。
準備工作
這個指南的例子使用Eclipse IDE實現。如果你使用Eclipse或其他IDE,如NetBeans,開啟它並建立一個新的Java專案。
如何做…
按以下步驟來實現這個例子:
1.建立Task類。指定它實現RecursiveTask類,並引數化為Integer型別。
public class Task extends RecursiveTask<Integer> {
2.宣告一個私有的、int型別陣列的屬性array。它將模擬在這個指南中,你將要處理的資料的陣列。
private int array[];
3.宣告兩個私有的、int型別的屬性start和end。這些屬性將決定這個任務要處理的陣列的元素。
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,檢查陣列的第4位置(索引號3)的元素是否在那個塊中。如果是這種情況,丟擲一個RuntimeException異常。然後,令這個任務睡眠1秒。
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),將這個元素塊分成兩個部分,建立2個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); }
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物件來處理這些塊。否則,他查詢陣列中的第4個位置的元素(索引號3)。如果這個元素在任務要處理的塊中,它丟擲一個RuntimeException異常。
當你執行這個程式,異常是丟擲了,但程式並沒有停止。在Main類中,你已經使用發起任務呼叫ForkJoinTask類的isCompletedAbnormally()方法。如果任務或它的子任務丟擲異常,這個方法返回true。你同時使用了同樣物件的getException()方法來獲取已丟擲的Exception物件。
當你在一個任務中丟擲一個未檢查異常時,它也影響到它的父任務(把它提交到ForkJoinPool類的任務)和父任務的父任務,以此類推。如果你修訂程式的所有輸出,你將會看到一些任務結束沒有輸出資訊。這些任務的開始資訊如下:
Task: Starting form 0 to 100
Task: Starting form 0 to 50
Task: Starting form 0 to 25
Task: Starting form 0 to 12
Task: Starting form 0 to 6
這些任務是那些及其父任務丟擲異常的任務。它們全部異常地完成。考慮到這一點,當你使用ForkJoinPool和ForkJoinTask物件開發一個程式,當你不想這種行為時,可以丟擲異常。
不止這些…
你可以獲取與這個例子相同的結果,如果不是丟擲異常,你可以使用ForkJoinTask類的completeExceptionally()方法。程式碼如下:
Exception e=new Exception("This task throws an Exception: "+ "Task from "+start+" to "+end); completeExceptionally(e);
參見
- 在第5章,Fork/Join框架中的建立一個Fork/Join池指南