簡介
從JDK1.7開始,Java提供ForkJoin框架用於並行執行任務,它的思想就是:將一個大任務分割成若干小任務,最終彙總每個小任務的結果得到這個大任務的結果。
整個流程需要三個類完成:
1、ForkJoinPool
既然任務是被逐漸的細化的,那就需要把這些任務存在一個池子裏面,這個池子就是ForkJoinPool。
它與其它的ExecutorService區別主要在於它使用“工作竊取“,那什麼是工作竊取呢?
一個大任務會被劃分成無數個小任務,這些任務被分配到不同的隊列,這些隊列有些幹活乾的塊,有些幹得慢。於是幹得快的,一看自己沒任務需要執行了,就去隔壁的隊列裏面拿去任務執行。
2、ForkJoinTask
ForkJoinTask就是ForkJoinPool裏面的每一個任務。他主要有兩個子類:RecursiveAction
和RecursiveTask
。然後通過fork()方法去分配任務執行任務,通過join()方法彙總任務結果,
-
RecursiveAction
一個遞歸無結果的ForkJoinTask(沒有返回值) -
RecursiveTask
一個遞歸有結果的ForkJoinTask(有返回值)
需要注意的是:這兩個子類都是抽象類,需要繼承實現。
舉例說明
我們舉個例子:如果要計算一個超大數組的和,最簡單的做法是用一個循環在一個線程內完成:
┌─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┐
└─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┘
還有一種方法,可以把數組拆成兩部分,分別計算,最後加起來就是最終結果,這樣可以用兩個線程並行執行:
┌─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┐
└─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┘
┌─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┐
└─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┘
如果拆成兩部分還是很大,我們還可以繼續拆,用4個線程並行執行:
┌─┬─┬─┬─┬─┬─┐
└─┴─┴─┴─┴─┴─┘
┌─┬─┬─┬─┬─┬─┐
└─┴─┴─┴─┴─┴─┘
┌─┬─┬─┬─┬─┬─┐
└─┴─┴─┴─┴─┴─┘
┌─┬─┬─┬─┬─┬─┐
└─┴─┴─┴─┴─┴─┘
這就是Fork/Join任務的原理:判斷一個任務是否足夠小,如果是,直接計算,否則,就分拆成幾個小任務分別計算。這個過程可以反覆“裂變”成一系列小任務。
編碼實現
整個任務流程如下所示:
- 首先繼承任務,覆寫任務的執行方法
- 通過判斷閾值,判斷該線程是否可以執行
- 如果不能執行,則將任務繼續遞歸分配,利用fork方法,並行執行
- 如果是有返回值的,才需要調用join方法,彙集數據。
RecursiveTask
這是一個有返回值的返回值的子類
public class RecursiveTaskTest {
private final static int MAX_THRESHOLD = 3;//設置一個任務處理最大的閾值
public static void main(String[] args) {
final ForkJoinPool joinPool = new ForkJoinPool();
ForkJoinTask<Integer> future = joinPool.submit(new CalculatedRecursiveTask(0, 1000));
try {
Integer integer = future.get();
System.out.println("執行結果:" + integer);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
private static class CalculatedRecursiveTask extends RecursiveTask<Integer> {
private final int start;//任務開始的上標
private final int end;//任務開始的下標
private CalculatedRecursiveTask(int start, int end) {
this.start = start;
this.end = end;
}
@Override
protected Integer compute() {
if (end - start <= MAX_THRESHOLD) {//如果自己能處理,就自己計算
return IntStream.rangeClosed(start, end).sum();
} else {//自己處理不了,拆分任務
int middle = (end + start) / 2;
CalculatedRecursiveTask leftTask = new CalculatedRecursiveTask(start, middle);
CalculatedRecursiveTask rightTask = new CalculatedRecursiveTask(middle + 1, end);
leftTask.fork();
rightTask.fork();
return leftTask.join() + rightTask.join();
}
}
}
}
結果:
執行結果:500500
RecursiveAction
這是一個沒有返回值的返回值的子類
public class ForkJoinRecursiveAction {
private final static int MAX_THRESHOLD = 3;//設置一個任務處理最大的閾值
private final static AtomicInteger SUM = new AtomicInteger();
public static void main(String[] args) throws InterruptedException {
ForkJoinPool forkJoinPool = new ForkJoinPool();
forkJoinPool.submit(new CalculateRecursiveAction(0,1000));
forkJoinPool.awaitTermination(10, TimeUnit.SECONDS);
System.out.println("執行結果爲:" + SUM);
}
private static class CalculateRecursiveAction extends RecursiveAction{
private final int start;
private final int end;
private CalculateRecursiveAction(int start, int end) {
this.start = start;
this.end = end;
}
@Override
protected void compute() {
if ((end-start)<=MAX_THRESHOLD){
SUM.addAndGet(IntStream.rangeClosed(start,end).sum());
}else {
int middle = (start+end)/2;
CalculateRecursiveAction leftAction = new CalculateRecursiveAction(start,middle);
CalculateRecursiveAction rightAction = new CalculateRecursiveAction(middle+1,end);
leftAction.fork();
rightAction.fork();
}
}
}
}
結果:
執行結果:500500
支持Runnable和Callable
public void execute(Runnable task) {
if (task == null)
throw new NullPointerException();
ForkJoinTask<?> job;
if (task instanceof ForkJoinTask<?>) // avoid re-wrap
job = (ForkJoinTask<?>) task;
else
job = new ForkJoinTask.RunnableExecuteAction(task);
externalPush(job);
}
public <T> ForkJoinTask<T> submit(Callable<T> task) {
ForkJoinTask<T> job = new ForkJoinTask.AdaptedCallable<T>(task);
externalPush(job);
return job;
}
從源碼上可以看到,即使不繼承兩個子類,也可以提交任務
總結
-
Fork/Join是一種基於“分治”的算法:通過分解任務,並行執行,最後合併結果得到最終結果。
-
ForkJoinPool線程池可以把一個大任務分拆成小任務並行執行,任務類必須繼承自RecursiveTask或RecursiveAction。
-
使用Fork/Join模式可以進行並行計算以提高效率。
-
Java標準庫提供的
java.util.Arrays.parallelSort(array)
可以進行並行排序,它的原理就是內部通過Fork/Join對大數組分拆進行並行排序,在多核CPU上就可以大大提高排序的速度。