掌握高并发、高可用架构
第二课 并发编程
从本课开始学习并发编程的内容。主要介绍并发编程的基础知识、锁、内存模型、线程池、各种并发容器的使用。
第十节 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();
}
}