掌握高併發、高可用架構
第二課 併發編程
從本課開始學習併發編程的內容。主要介紹併發編程的基礎知識、鎖、內存模型、線程池、各種併發容器的使用。
第十節 Fork/Join框架
Fork/Join
分而治之
ForkJoinPool
基本思想
ThreadPoolExecutor
線程池中每個任務都由單個線程獨立處理。如果出現一個非常耗時的任務,就會出現線程池中只有一個線程在處理這個大任務,而其他線程卻空閒着。這會導致CPU負載不均衡。
ForkJoinPool
,將一個大任務拆分成多個小任務,使用fork將小任務分發給其他線程同時處理,然後使用join將小任務的執行結果彙總。它利用多處理器的優勢,集中所有可用的處理能力來增強執行效率,這就是分而治之思想的並行實現。
基本原理
ForkJoinPool
也是ExecutorService
接口的實現類。
ForkJoinPool
的兩大核心是分而治之和工作竊取(Work Stealing)算法。
工作竊取算法
算法思想:
- 每個線程都有自己的WorkQueue,它是一個雙端隊列
- 隊列支持三個功能,push、pop、poll
- push/pop只能被隊列的所有者線程使用,而poll能被其他線程調用
- 劃分的子任務調用fork時,都會被push到線程的隊列中
- 默認情況下,線程從自己的隊列中獲取任務並執行
- 當自己的隊列爲空時,隨機從另外的線程的隊列末尾調用poll竊取任務
創建ForkJoinPool對象
- 調用
Executors
工具類
// parallelism定義並行級別
public static ExecutorService newWorkStealingPool(int parallelism);
// Runtime.getRuntime().availableProcessors()爲並行級別
public static ExecutorService newWorkStealingPool();
- 調用ForkJoinPool內部方法commonPool()
public static ForkJoinPool commonPool();
- 調用構造器
ForkJoinTask
大多數時候,我們都是提交ForkJoinTask
到FormJoinPool
。
以下是ForkJoinTask的三個核心方法:
- fork(),大任務劃分爲小任務後,調用小任務的fork()方法可以將任務放入線程池中
- join(),調用小任務的join()方法等待任務的返回結果。如果子任務拋出異常,join也會拋出異常;有方法
quietlyJoin()
不會拋出異常也不會返回結果,需要調用getException()
和getResult()
- invoke(),在當前線程中同步執行該任務
RecursiveAction
和RecursiveTask
通常我們不會直接使用ForkJoinTask,而是使用它的兩個抽象類:
RecursiveAction
:沒有返回值的任務RecursiveTask
:有返回值的任務
public class RecursiveActionTeset {
static class Sorter extends RecursiveAction {
public static void sort(long[] array) {
ForkJoinPool.commonPool().invoke(new Sorter(array, 0, array.length));
}
private final long[] array;
private final int lo, hi;
public Sorter(long[] array, int lo, int hi) {
this.array = array;
this.lo = lo;
this.hi = hi;
}
private static final int THRESHOLD = 1000;
// 大任務拆分的方法
@Override
protected void compute() {
if (hi - lo < 1000) {
Arrays.sort(array, lo, hi);
} else {
int mid = (hi + lo) >>> 1;
// 長度大於1000時,平均分成兩個數組
Sorter left = new Sorter(array, lo, mid);
Sorter right = new Sorter(array, mid, hi);
invokeAll(left, right);
merge(lo, mid, hi);
}
}
private void merge(int lo, int mid, int hi) {
long[] buff = Arrays.copyOfRange(array, lo, mid);
for (int i = 0, j = lo, k = mid; i < buff.length; i++) {
if (k == hi || buff[i] < array[k]) {
array[j] = buff[i++];
} else {
array[j] = array[k++];
}
}
}
public static void main(String[] args) {
long[] array = new Random().longs(100_0000).toArray();
Sorter.sort(array);
System.out.println(Arrays.toString(array));
}
}
}
public class BatchInsertTask extends RecursiveTask<Integer> {
//要插入的數據
List<Integer> records;
public BatchInsertTask(List<Integer> records) {
this.records = records;
}
@Override
protected Integer compute() {
//當要插入的數據少於5,則直接插入
if (records.size() < 5) {
return computeDirectly();
} else {
//如果要插入的數據大於等於5,則進行分組插入
int size = records.size();
//第一個分組
BatchInsertTask aTask = new BatchInsertTask(records.subList(0, size / 2));
//第二個分組
BatchInsertTask bTask = new BatchInsertTask(records.subList(size / 2, records.size()));
//兩個任務併發執行起來
invokeAll(aTask, bTask);
//兩個分組的插入的行數加起來
return aTask.join() + bTask.join();
}
}
/**
* 真正插入數據的邏輯
*/
private int computeDirectly() {
try {
Thread.sleep((long) (records.size() * 1000));
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("插入了:" + Arrays.toString(records.toArray()));
return records.size();
}
}