線程基礎、線程之間的共享和協作
(目前會將一些概念簡單描述,一些重點的點會詳細描述)
學習目標:多線程的併發工具類(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教育課後總結。