Fork-Join架構(Java 7)
Java在JDK7之後加入了並行計算的架構Fork/Join,Fork/Join採用的是分治法,Fork 是將一個大任務拆分成若干個子任務,子任務分別去計算,而 Join 是擷取到子任務的計算結果,然後合并,這個是遞迴的過程。子任務被分配到不同的核上執行時,Fork-Join架構很適合使用於解決運行在多核心處理器上的並發問題;
總的來說Fork-Join模式的核心是
分治,Fork分解任務,Join收集資料;
Fork/Join架構的核心類
Fork-Join架構有幾個主要的核心類:
ForkJoinPool類:線程池類,負責運行子任務,繼承自ExecutorService,可按照Java5的ExecutorServic的方式去調用它; ForkJoinPool 常用構造器:
ForkJoinPool(int parallelism); //建立一個parallelism個並行線程的ForkJoinPool ForkJoinPool(); // 以Runtime.avaliableProcessors()方法的傳回值作為並行線程數量參數 ForkJoinPool提交線程的主要方法: submit,execute,invoke;
| |
用戶端PorkJoinPool線程池提交任務 |
ForkJoinTask任務類內部調用任務 |
| 非同步執行 |
execute(ForkJoinTask) |
ForkJoinTask.fork |
| 等待擷取結果 |
invoke(ForkJoinTask) |
ForkJoinTask.invoke |
| 執行,擷取Future |
submit(ForkJoinTask) |
ForkJoinTask.fork(ForkJoinTask are Futures) |
ForkJoinTask類:任務類的預設實現,實際一般使用的它的兩個預設實現:
RecurisiveAction :沒有傳回值的任務類抽象類別。
RecurisiveTask :帶有傳回值的任務類抽象類別。 ForkJoinTask的兩個主要方法 fork 提交子任務,join 擷取子任務執行結果
※Fork-Join 架構任務的執行由ForkJoinTask類的對象之外,還可以使用一般的Callable和Runnable介面來表示任務。
異常處理
ForkJoinTask在執行的時候可能會拋出異常,但是沒辦法在主線程裡直接捕獲異常,所以ForkJoinTask提供了 i
sCompletedAbnormally() 方法來檢查任務是否已經拋出異常或已經被取消了,並且可以通過ForkJoinTask的
getException 方法擷取異常。使用如下代碼:
if(task.isCompletedAbnormally()) { System.out.println(task.getException()); } getException方法返回Throwable對象,如果任務被取消了則返回CancellationException。如果任務沒有完成或者沒有拋出異常則返回null。
程式碼範例
樣本1: RecursiveTask 有傳回值任務類 簡要類比 Java 8 parallelStream並行資料流的sum方法(為了便於理解使用二分法實現分割,JDK中使用的是更加複雜的分割演算法); 使用 RecursiveTask 分治有傳回值的線程任務:並行計算List的sum值,在將其分割到小於閥值後再由子線程進行計算;
//子任務類 public static class SumTask extends
RecursiveTask<Long> { private static final int THRESHOLD = 100; //規定分割閥值 private List<Long>list; private long low; private long high; public SumTask(List<Long> list,long low,long high){ this.list = list; this.low = low; this.high = high; } @Override //覆蓋compute介面,規定分治策略(採用二分法) protected Long compute() { long sum = 0; if(high - low +1 <= THRESHOLD){ sum = list.stream().skip(low).limit(high-low+1).mapToLong(x->x).sum(); //流長度小於閾值直接計算sum }else{ //流長度大於閾值,對流進行分治 long mid = (low + high) /2;
SumTask
leftSubTask = new SumTask(list,low,mid); SumTask
rightSubTask = new SumTask(list,mid+1,high); //分發運行子線程
leftSubTask.fork(); rightSubTask.fork(); //或者使用 invoke(lefSubTask,rightSubTask);代替以上任務提交的兩行; //擷取子線程結果