1、Fork-Join
Java下多線程的開發我們可以自己啓用多線程、線程池,還可以使用ForkJoin。 ForkJoin 可以讓我們不去了解諸如 Thread、Runnable 等相關的知識,只要遵循 ForkJoin 的開發模式,就可以寫出很好的多線程併發程序。
2、Fork-Join體現了分而治之
什麼是分而治之?一種設計思想、策略。十大計算機經典算法:快速排序、堆排序、歸併排序、二分查找、線性查找、深度優先、廣度優先、Dijktra、動態規劃、樸素貝葉斯分類。其中有三個就屬於分而治之的設計思想,分別是快速排序、歸併排序、二分查找。
分而治之的設計思想是:將一個難以解決的問題,拆分成一些規模較小的問題,以便各個擊破,達到分而治之的目的。
3、Fork-Join原理
在必要的情況下,將一個大任務,進行拆分(Fork)成若干個小任務,直到不可再拆時,將一個個小任務運算結果進行彙總(Join),最終達到想要的結果,如下圖所示。
4、Fork-Join實戰
使用 ForkJoin 框架,必須首先創建一個 ForkJoin 任務。它提供在任務中執行Fork 和 Join 的操作機制,通常我們不直接繼承 ForkjoinTask 類,只需要直接繼承其子類。
- RecursiveAction,用於沒有返回結果的任務
- RecursiveTask,用於有返回值的任務
Task 要通過 ForkJoinPool 來執行,使用 invoke或者submit或者execute 提交,三者的區別是:invoke 是同步執行,調用之後需要等待任務完成,才能執行後面的代碼; submit和execute 是異步執行。 join()和 get() 方法當任務完成的時候返回計算結果。
任務繼承RecursiveAction或者RecursiveTask類,重寫父類的compute()方法,我們自己的業務寫在compute方法裏即可,首先需要判斷任務是否足夠小,如果足夠小就直接執行任務。如果不足夠小,就必須分割成兩個子任務,每個子任務在調用invokeAll 方法時,又會進入 compute 方法,看看當前子任務是否需要繼續分割成孫任務,如果不需要繼續分割,則執行當前子任務並返回結果。使用 join 方法會等待子任務執行完並得到其結果。
- Fork/Join的同步用法同時演示返回結果值:統計整形數組中所有元素的和
package cn.lspj.ch2.forkjoin.sum;
import java.util.Random;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.RecursiveTask;
/**
* 類說明:統計整形數組中所有元素的和
*/
public class SumToForkJoin extends RecursiveTask<Integer> {
//數組長度
public static final int ARRAY_LENGTH = 4000;
/*閾值*/
private final static int THRESHOLD = ARRAY_LENGTH / 10;
private int[] array;
private int fromIndex;
private int toIndex;
public SumToForkJoin(int[] array, int fromIndex, int toIndex) {
this.array = array;
this.fromIndex = fromIndex;
this.toIndex = toIndex;
}
@Override
protected Integer compute() {
// 判斷任務的大小是否合適,當數組長度小於設定的某個閾值時,求該數組的和
if (toIndex - fromIndex < THRESHOLD) {
int count = 0;
for (int i = fromIndex; i < toIndex; i++) {
count += array[i];
}
return count;
}
// 數組長度不滿足設定的閾值,繼續拆分數組
int mid = (fromIndex + toIndex) / 2;
SumToForkJoin left = new SumToForkJoin(array, fromIndex, mid);
SumToForkJoin right = new SumToForkJoin(array, mid + 1, toIndex);
// 調用invokeAll方法繼續執行compute方法
invokeAll(left, right);
return left.join() + right.join();
}
/**
* 隨機生成一個數組
*
* @return
*/
private static int[] makeArray() {
//new一個隨機數發生器
Random r = new Random();
int[] result = new int[ARRAY_LENGTH];
for (int i = 0; i < ARRAY_LENGTH; i++) {
//用隨機數填充數組
result[i] = r.nextInt(ARRAY_LENGTH * 3);
}
return result;
}
public static void main(String[] args) {
// new 出一個pool實例,用來執行task任務
ForkJoinPool pool = new ForkJoinPool();
// 生成一個隨機值的數組
int[] array = makeArray();
// new 出一個task任務,交給pool去執行
SumToForkJoin task = new SumToForkJoin(makeArray(), 0, array.length);
// 調用pool的invoke方法,同步執行
pool.invoke(task);
// 調用task的join方法,獲取數組求和後的值,並輸出
System.out.println("count=" + task.join());
}
}
執行結果如下:
- Fork/Join的異步用法同時演示不要求返回值:遍歷指定目錄(含子目錄)尋找指定類型文件
package cn.lspj.ch2.forkjoin;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.RecursiveAction;
/**
*類說明:遍歷指定目錄(含子目錄)找尋指定類型文件
*/
public class FindDirsFilesToForkJoin extends RecursiveAction {
private File path;
public FindDirsFilesToForkJoin(File path) {
this.path = path;
}
@Override
protected void compute() {
List<FindDirsFilesToForkJoin> subTasks = new ArrayList<>();
File[] files = path.listFiles();
if (files!=null){
for (File file : files) {
if (file.isDirectory()) {
// 對每個子目錄都新建一個子任務。
subTasks.add(new FindDirsFilesToForkJoin(file));
} else {
// 遇到文件,檢查。
if (file.getAbsolutePath().endsWith("txt")){
System.out.println("文件:" + file.getAbsolutePath());
}
}
}
if (!subTasks.isEmpty()) {
// 在當前的 ForkJoinPool 上調度所有的子任務。
for (FindDirsFilesToForkJoin subTask : invokeAll(subTasks)) {
subTask.join();
}
}
}
}
public static void main(String [] args){
try {
// 用一個 ForkJoinPool 實例調度總任務
ForkJoinPool pool = new ForkJoinPool();
FindDirsFilesToForkJoin task = new FindDirsFilesToForkJoin(new File("F:/Program Files"));
/*異步提交*/
pool.execute(task);
/*主線程做自己的業務工作*/
System.out.println("Task is Running......");
Thread.sleep(1);
int otherWork = 0;
for(int i=0;i<100;i++){
otherWork = otherWork+i;
}
System.out.println("Main Thread done sth......,otherWork="
+otherWork);
task.join();//阻塞方法
System.out.println("Task end");
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
執行結果如下:
從執行結果中不難看出,使用execute方法異步調用執行task,主線程不會等待子線程先執行,而是繼續完成主線程的業務工作。
備註:博主微信公衆號,不定期更新文章,歡迎掃碼關注。