1.fork/join示例
package com.zskj.concert.test.forkJoin;
import java.util.concurrent.RecursiveTask;
/**
* @author cw
* @date 2019/8/29
*/
public class ForkJoinSumCalculator extends RecursiveTask<Long> {
private final long[] numbers;
private final int start;
private int end;
public static final long THRESHOLD = 10L;
public ForkJoinSumCalculator(long[] numbers) {
this(numbers,0,numbers.length);
}
public ForkJoinSumCalculator(long numbers[], int start, int end) {
this.numbers = numbers;
this.start = start;
this.end = end;
}
//負責求和
@Override
protected Long compute() {
int length =end-start;
if(length <=THRESHOLD){
return computeSequentaily();
}
ForkJoinSumCalculator leftTask = new ForkJoinSumCalculator(numbers, start, start + length / 2);
leftTask.fork();
ForkJoinSumCalculator rightTask = new ForkJoinSumCalculator(numbers, start + length / 2, end);
Long rightResult = rightTask.compute();
Long leftResult = leftTask.join();
return leftResult+rightResult;
}
//計算求和
private Long computeSequentaily() {
long sum = 0;
for (int i = 0; i < end; i++) {
sum += numbers[i];
}
return sum;
}
}
//java8調用示例
public static long forkJoinSum(long n){
long[] numbers = LongStream.rangeClosed(1, n).toArray();
ForkJoinSumCalculator task = new ForkJoinSumCalculator(numbers);
return new ForkJoinPool().invoke(task);
}
2使用分支/合併的最佳做法
- 對於一個任務調用join方法會阻塞調用方,直到該任務做出結果
- 不應該在ResucursiveTask內部使用ForkJoinPool的invoke方法。應該始終調用compute或者fork方法,只有順序代碼才應該用invoke來啓動計算。
- 不應該認爲並行流以及分支/合框架在多個處理器上使用比順序計算快。
3.工作竊取技術
- 問題:理想情況下,劃分並行任務時,應該讓每個任務用完全相同的時間完成,讓所有的CPU內核同樣繁忙。但是實際中,每個子任務所花的時間並不相同,
- 分支/合併框架用一種稱爲工作竊取的技術來解決問題。
- 工作原理:每個線程都爲分配給他的任務保存一個雙向隊列,每完成一個任務,就從隊列頭上取出下一個任務開始執行。有點可能早早完成,但是並沒有閒下來。而是隨機選取一個別的線程,從隊列的尾部拿取一個任務,直到所有的線程都結。
- 一般來說,這種工作竊取算法用於池中的工作線程之間的重新分配和平衡任務。