从本课开始学习并发编程的内容。主要介绍并发编程的基础知识、锁、内存模型、线程池、各种并发容器的使用。
Fork/Join
分而治之
ForkJoinPool
ThreadPoolExecutor
线程池中每个任务都由单个线程独立处理。如果出现一个非常耗时的任务,就会出现线程池中只有一个线程在处理这个大任务,而其他线程却空闲着。这会导致CPU负载不均衡。
ForkJoinPool
,将一个大任务拆分成多个小任务,使用fork将小任务分发给其他线程同时处理,然后使用join将小任务的执行结果汇总。它利用多处理器的优势,集中所有可用的处理能力来增强执行效率,这就是分而治之思想的并行实现。
ForkJoinPool
也是ExecutorService
接口的实现类。
ForkJoinPool
的两大核心是分而治之和工作窃取(Work Stealing)算法。
算法思想:
Executors
工具类// parallelism定义并行级别
public static ExecutorService newWorkStealingPool(int parallelism);
// Runtime.getRuntime().availableProcessors()为并行级别
public static ExecutorService newWorkStealingPool();
public static ForkJoinPool commonPool();
ForkJoinTask
大多数时候,我们都是提交ForkJoinTask
到FormJoinPool
。
以下是ForkJoinTask的三个核心方法:
quietlyJoin()
不会抛出异常也不会返回结果,需要调用getException()
和getResult()
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 {
//要插入的数据
List records;
public BatchInsertTask(List 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();
}
}