多執行緒學習-day-05 ForkJoin概念及使用
執行緒基礎、執行緒之間的共享和協作
(目前會將一些概念簡單描述,一些重點的點會詳細描述)
學習目標:多執行緒的併發工具類(1)
用途,概念:
ForkJoinPool的優勢在於,利用多核CPU,將一個任務,拆分成多個小任務 ,將這些小任務分配到多個處理器上並行執行;當小任務都執行完成之後,再將結果進行合併彙總。每個小任務間都沒有關聯,與原任務的形式相同。體現了“分而治之”的概念。任務遞迴分配成若干個小任務 -- 並行求值 -- 結果合併。
1、ForkJoinPool
Java7提供了ForkJoinPool來支援將一個任務拆分成多個小任務進行平行計算,再將多個“小任務”的結果進行join彙總。
2、invoke、invokeAll
執行指定的任務,等待任務,完成任務返回結果。
3、遞迴演算法
在繼承RecurisiveTask(有返回結果),RecurisiveAction(無返回結果)類時,通過遞迴的方式,來將任務拆分成一個一個的小任務,通過invokeAll()方法來排程子任務,等待任務完成返回結果。注意,只有在ForkJoinPool執行計算過程中呼叫它們。
先來看一個例子:
通過計算10000000組整型數的求和,用兩種方式:普通單執行緒演算法求和,利用ForkJoinPool多執行緒遞迴的求和。
定義一個隨機產生10000000的整型陣列的類
public class MakeArray { // 設定一個final型別的整型數,表示要操作的數量 public static final int MAX_ARRAY = 1000000; // 定義一個方法,隨機產生MAX_ARRAY個數 public static int[] makeArray() { // 設定一個Random類,作為隨機生產數作用 Random rand = new Random(); // 定義一個整型陣列,能儲存MAX_ARRAY個數 int[] array = new int[MAX_ARRAY]; for (int i = 0; i < MAX_ARRAY; i++) { // 往array數組裡面隨機新增一個數 array[i] = rand.nextInt(MAX_ARRAY * 2); } // 返回 return array; } }
定義一個普通的單執行緒求和類
public class SumNormal {
// 普通累加方法
public static void main(String[] args) {
// 定義時間
long startTime = System.currentTimeMillis();
// 定義一個數組,隨機生成MAX_ARRAY個整型數
int[] array = MakeArray.makeArray();
// 定義一個sum變數進行累加統計
int sum = 0;
// 求和
for (int i = 0; i < array.length; i++) {
sum += array[i];
}
// 輸出
System.out.println(
MakeArray.MAX_ARRAY + "個數,總和為sum = " + sum + "。共耗時:" + (System.currentTimeMillis() - startTime));
}
}
控制檯輸出結果:
1000000個數,總和為sum = -1369059355。共耗時:28
定義多執行緒求和類
public class SumArray {
private static class SumTask extends RecursiveTask<Integer> {
private static final long serialVersionUID = 1L;
// 定義一個閾值
private static final int THERHOLD = MakeArray.MAX_ARRAY / 10;
// 定義一個要操作的陣列
private int[] sum;
// 定義起始下標
private int start;
// 定義結尾下標
private int end;
public SumTask(int[] sum, int start, int end) {
this.sum = sum;
this.start = start;
this.end = end;
}
@Override
protected Integer compute() {
if (end - start < THERHOLD) {
int count = 0;
for (int i = start; i < end; i++) {
count += sum[i];
}
return count;
} else {
int mid = (start + end) / 2;
SumTask sumTaskA = new SumTask(sum, start, mid);
SumTask sumTaskB = new SumTask(sum, mid + 1, end);
// 將執行緒阻塞,必須所有任務執行完成之後,統一返回
invokeAll(sumTaskA, sumTaskB);
return sumTaskA.join() + sumTaskB.join();
}
}
}
public static void main(String[] args) {
long startTime = System.currentTimeMillis();
System.out.println("執行緒開始執行...");
ForkJoinPool pool = new ForkJoinPool();
int[] sum = MakeArray.makeArray();
SumTask sumTask = new SumTask(sum, 0, sum.length - 1);
pool.invoke(sumTask);
System.out.println(MakeArray.MAX_ARRAY + "個數據,總和 sum = " + sumTask.join() + ", 共耗時:"
+ (System.currentTimeMillis() - startTime));
}
}
控制檯輸出結果:
執行緒開始執行...
1000000個數據,總和 sum = -941995665, 共耗時:35
我們發現:
單執行緒求和共耗時:28ms
多執行緒求和共耗時:35ms
這樣可以給我們一種警示,並非引入多執行緒就一定能提升效率,而是要看實際情況,經過多輪測試選擇合適的方式來進行操作。
可以引申出其他的場景:
統計某個系統下的資料夾數量、超大規模的一些操作,例如幾百萬,上千萬的資料庫等操作等等場景。大家都可以根據不同的場景進行多多測試。個人感覺未來這種多執行緒遞迴操作會用的很多的地方,希望通過學習拓展思路
來自享學IT教育課後總結。