目录
1.原理
将一个大任务拆分(fork)成若干个小任务,再将小任务运算的结果进行(join)汇总。(分而自治的思想)
2.使用场景
适合计算密集型场景;
3.使用范式
3.1 定义ForkJoinPool
ForkJoinPool pool = new ForkJoinPool();
定义ForkJoinPool,默认线程个数为CPU核数。
3.2 自定义任务
ForkJoinTest forkJoinTest = new ForkJoinTest(1, 100000000);
继承RecursiveTask有返回值;
继承RecursiveAction无返回值;
3.3 执行任务
pool.submit(forkJoinTest) && pool.invoke(forkJoinTest)
invoke是同步执行,submit是异步执行。
3.4 获取结果
forkJoinTest.get(),阻塞方法。
4.代码示例
4.1 计算
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.RecursiveTask;
/**
* 计算1+2+3+4+.....的值
*/
public class ForkJoinTest extends RecursiveTask<Integer> {
public static int standard = 2;
private int start;
private int end;
private int sum = 0;
public ForkJoinTest(int start, int end) {
this.start = start;
this.end = end;
}
//计算
@Override
protected Integer compute() {
//当每组的数值个数小于等于standard 2 时,进行累加操作
boolean flag = start - end <= standard;
if (flag) {
for (int i = start; i <= end; i++) {
sum += i;
}
} else {
//二分
int mid = (start + end) / 2;
ForkJoinTest left = new ForkJoinTest(start, mid);
ForkJoinTest right = new ForkJoinTest(mid + 1, end);
// invokeAll(left, right);
left.fork(); //拆分计算
right.fork();//拆分计算
sum = left.join() + right.join();//合并计算结果值
}
return sum;
}
public static void main(String[] args) throws Exception {
//定义ForkJoinPool
ForkJoinPool pool = new ForkJoinPool();
//自定义任务
ForkJoinTest forkJoinTest = new ForkJoinTest(1, 100000000);
//同步执行任务
// pool.invoke(forkJoinTest);
//异步执行任务
pool.submit(forkJoinTest);
//获取结果
System.out.println(forkJoinTest.get());
}
}
4.2 文件查找
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.RecursiveAction;
/**
* 查找文件中.txt后缀的文件,并打印出来
*/
public class ForkJoinTest2 extends RecursiveAction {
private File file;
//用于封装子任务
public List<ForkJoinTest2> list = new ArrayList<>();
public ForkJoinTest2(File file) {
this.file = file;
}
@Override
protected void compute() {
File[] files = file.listFiles();
if (files != null) {
for (File f : files) {
if (f.isDirectory()) {
//为每一个目录创建一个子任务
list.add(new ForkJoinTest2(f));
} else {
//遇到文件则检查,打印出后缀为.txt的文件
if (f.getAbsolutePath().endsWith(".txt")) {
System.out.println(f.getAbsolutePath());
}
}
}
if (!list.isEmpty()) {//在当前的ForkJoinPool调度所有的子任务
invokeAll(list);
for (ForkJoinTest2 forkJoinTest2 : list) {
forkJoinTest2.join();
}
//invokeAll效率要比逐个fork高得多
/* for(ForkJoinTest2 forkJoinTest2:list){
forkJoinTest2.fork();
}
for(ForkJoinTest2 forkJoinTest2:list){
forkJoinTest2.join();
}*/
}
}
}
public static void main(String[] args) {
long startTime = System.currentTimeMillis();
ForkJoinPool pool = new ForkJoinPool();
ForkJoinTest2 forkJoinTest2 = new ForkJoinTest2(new File("D:\\PCIdownload"));//尽量找层级深的那个目录测试
pool.invoke(forkJoinTest2);
long endTime = System.currentTimeMillis();
System.out.println(endTime - startTime);
findTxt(new File("D:\\PCIdownload"));
System.out.println(System.currentTimeMillis() - endTime);
}
public static void findTxt(File file) {
if (file.isDirectory()) {
File[] files = file.listFiles();
if (files != null) {
for (File file1 : files) {
findTxt(file1);
}
}
}else {
if(file.getAbsolutePath().endsWith(".txt")){
System.out.println(file.getAbsolutePath());
}
}
}
}
5.注
1.ForkJoinPool的每个工作线程维护这一个工作队列(workQueue);新的任务放在队尾,消费时从队尾获取;其他线程从队头窃取工作任务。
2.工作窃取机制;本线程任务执行完,会去窃取别的线程的任务;而不像线程池+countDownLatch;线程执行完当前任务后会阻塞。
3.invokeAll()方法性能比fork方法更好。Pool里面线程数量是固定的,那么调用子任务的fork方法相当于A先分工给B,然后A当监工不干活,B去完成A交代的任务。所以上面的模式相当于浪费了一个线程。那么如果使用invokeAll相当于A分工给B后,A和B都去完成工作。