1. 程式人生 > >多執行緒學習-day-05 ForkJoin概念及使用

多執行緒學習-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教育課後總結。