1. 程式人生 > >Fork/Join框架(五)在任務中丟擲異常

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物件開發一個程式,當你不想這種行為時,可以丟擲異常。

以下截圖顯示了這個例子執行的一部分:
4

不止這些…

你可以獲取與這個例子相同的結果,如果不是丟擲異常,你可以使用ForkJoinTask類的completeExceptionally()方法。程式碼如下:

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

參見

  • 在第5章,Fork/Join框架中的建立一個Fork/Join池指南