ForkJoinPool类实现了ExecutorService接口,因此也属于线程池,是一种特殊的线程池。
ForkJoinPool这个工具类从Java7 才开始提供的,优势在于,可以充分利用多cpu,多核cpu的优势,把一个大任务(fork)成若干小任务,把若干小任务放到多个处理器核心上并行执行;当这些小任务执行完成之后,最终汇总(Join)每个小任务结果后得到大任务结果的框架。从而实现用少量的线程,完成大数量的任务。
1、ThreadPoolExecutor 与 ForkJoinPool区别
ForkJoinPool与ThreadPoolExecutor两个都实现了Executor和ExecutorService接口。
ForkJoinPool它使用了一个无限队列来保存需要执行的任务,而线程的数量则是通过构造函数传入,如果没有向构造函数中传入希望的线程数量,那么当前计算机可用的CPU数量会被设置为线程数量作为默认值。使用ForkJoinPool能够使用数量有限的线程来完成非常多的具有父子关系的任务,
ThreadPoolExecutor能够提高线程的可管理性,通过重用已存在的线程,降低线程创建和销毁造成的消耗;提高系统响应速度。但是,由于ThreadPoolExecutor中的Thread无法选择优先执行子任务,所以不能完成父子关系的任务。
2、使用方法
创建 ForkJoinPool 实例,然后调用ForkJoinPool的 submit(ForkJoinTask
ForkJoinTask是一个抽象类,它还有两个抽象子类:RecusiveAction和RecusiveTask。其中RecusiveTask代表有返回值的任务,而RecusiveAction代表没有返回值的任务。
|
submit(ForkJoinTask 提交forkjointask执行。 |
ForkJoinTask> |
submit(Runnable task) 提交执行一个Runnable任务并返回一个表示该任务的未来。 |
|
submit(Runnable task, T result) 提交执行一个Runnable任务并返回一个表示该任务的未来。 |
|
invoke(ForkJoinTask 完成给定的任务,完成后返回其结果。 |
|
invokeAll(Collection extends Callable 执行给定的任务,返回一个未来持有他们的状态和结果的列表时,所有的完整。 |
boolean |
awaitTermination(long timeout, TimeUnit unit) 直到所有的任务都完成后,关闭请求,或超时发生,或当前线程被中断,以先发生的情况。
|
static ForkJoinPool |
commonPool() 返回公用池实例。 |
实例一:没有返回值的“大任务”(打印1-3000之间的整数)- RecusiveAction
public class ForkJoinPoolDemo1 {
public static void main(String[] args) throws Exception {
PrintTask task = new PrintTask(1, 300);
// 创建ForkJoinPool实例,并执行分割任务,
// 若参数为空,则当前计算机可用的CPU数量会被设置为线程数量作为默认值
ForkJoinPool pool = new ForkJoinPool(4);
pool.submit(task);
// 线程阻塞,等待所有任务完成
pool.awaitTermination(2, TimeUnit.SECONDS);
// 关闭线程池
pool.shutdown();
}
}
class PrintTask extends RecursiveAction {
private static final int THRESHOLD = 50; //一次最多只能打印50个数
private int start;
private int end;
public PrintTask(int start, int end) {
this.start = start;
this.end = end;
}
@Override
protected void compute() {
if (end - start < THRESHOLD) {
for (int i = start; i < end; i++) {
System.out.println(Thread.currentThread().getName() + "的i值:" + i);
}
} else {
// 把大任务对半拆分成小任务,使用递归
int middle = (start + end) / 2;
PrintTask left = new PrintTask(start, middle);
PrintTask right = new PrintTask(middle, end);
//并行执行两个小任务
left.fork();
right.fork();
}
}
}
实例二:有返回值的“大任务”(对一个长度为300的数组元素进行累加求和)- RecusiveTask
public class ForkJoinPoolDemo2 {
public static void main(String[] args) throws Exception {
int[] arr = new int[300];
Random random = new Random();
int total = 0;
// 初始化100个数组元素
for (int i = 0, len = arr.length; i < len; i++) {
int temp = random.nextInt(20);
// 对数组元素赋值,并将数组元素的值添加到sum总和中
total += (arr[i] = temp);
}
System.out.println("初始化数组总和:" + total);
SumTask task = new SumTask(arr, 0, arr.length);
// 创建ForkJoinPool,这个是jdk1.8提供的功能
ForkJoinPool pool = ForkJoinPool.commonPool();
Future future = pool.submit(task); //提交分解的SumTask 任务
System.out.println("多线程执行结果:" + future.get());
//关闭线程池
pool.shutdown();
}
}
class SumTask extends RecursiveTask {
private static final int THRESHOLD = 20; //每个小任务 最多只累加20个数
private int arry[];
private int start;
private int end;
public SumTask(int[] arry, int start, int end) {
super();
this.arry = arry;
this.start = start;
this.end = end;
}
@Override
protected Integer compute() {
int sum = 0;
//当end与start之间的差小于threshold时,开始进行实际的累加
if (end - start < THRESHOLD) {
for (int i = start; i < end; i++) {
sum += arry[i];
}
return sum;
} else {
//当end与start之间的差大于threshold,即要累加的数超过20个时候,将大任务分解成小任务
int middle = (start + end) / 2;
SumTask left = new SumTask(arry, start, middle);
SumTask right = new SumTask(arry, middle, end);
//并行执行两个小任务
left.fork();
right.fork();
//把两个小任务累加的结果合并起来
return left.join() + right.join();
}
}
}
参考文章:
ForkJoinPool 分支/ 合并框架实战与原理分析
—— Stay Hungry. Stay Foolish. 求知若饥,虚心若愚。