一、使用场景
大任务拆解成多个子任务,子任务还可以继续拆解成更小的子任务,最后将这些最小的子任务用多个线程并行执行,然后合并执行结果,例如,对超过1000万个元素的数组进行排序。
需求:有一个大数据量的用户List,根据其部门id,设置部门名称。
二、基本思想
ForkJoin模型利用了分治算法的思想,将大任务不断拆解,多线程执行,最后合并结果。它的本质是一个线程池。
二、工作逻辑
每一个工作线程维护一个本地的双端队列用来存放任务。线程在运行的过程中产生新的任务(通常是因为调用了 fork())时,会放入工作队列的队尾,并且工作线程在处理自己的工作队列时,从队尾取出任务来执行。当某个工作线程的本地队列为空时,它会尝试窃取一个任务,也叫工作窃取(可能是来自于刚刚提交到 pool 的任务,也可能是来自于其他工作线程的工作队列),窃取其他线程的工作队列的任务时,从队首取出,加到自己的队列中,然后执行下面两步:
- 如果任务足够小就直接执行。
- 否则将任务拆分成更小的子任务。
从上面的过程可以看出,Fork join并不是预先拆分所有任务,而是在执行时动态的决定拆分。
ForkJoin有三个比较重要的方法(操作)
1. fork:开启一个新线程(或是重用线程池内的空闲线程),将任务交给该线程处理。
2. join:等待子任务的处理线程处理完毕,获得返回值。
3. compute:拆解并执行任务
三、代码如下
User.java
package com.example.demo.util;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class User {
private Integer id;
private String name;
private Integer deptId;
private String deptName;
}
多线程实现
package com.example.demo.util;
import com.google.common.collect.Maps;
import lombok.SneakyThrows;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.ForkJoinTask;
import java.util.concurrent.RecursiveTask;
public class QTaskUser extends RecursiveTask<List<User>> {
// 分片阈值
public static final int threshold = 10;
private int start;
private int end;
private List<User> list;
private Map<Integer, String> deptNameMap;
public QTaskUser(int start, int end, List<User> list, Map<Integer, String> deptNameMap) {
this.start = start;
this.end = end;
this.list = list;
this.deptNameMap = deptNameMap;
}
/**
*
*/
@Override
protected List<User> compute() {
// 小于阈值直接执行
if (end - start <= threshold) {
for (int i = start; i < end; i++) {
User user = this.list.get(i);
String deptName = deptNameMap.get(user.getDeptId());
user.setDeptName(deptName);
}
} else {
// 递归拆解任务
int middle = (start + end) / 2;
QTaskUser leftTask = new QTaskUser(start, middle, list, deptNameMap);
QTaskUser rightTask = new QTaskUser(middle, end, list, deptNameMap);
invokeAll(leftTask, rightTask);
// 等待计算完成并返回计算结果。
leftTask.join();
rightTask.join();
}
return list;
}
@SneakyThrows
public static void main(String[] args) {
List<User> list = new ArrayList<>();
Map<Integer, String> map = Maps.newHashMap();
for (int i = 0; i < 10000009; i++) {
User user = User.builder().id(i).deptId(i).name(i + "name").build();
list.add(user);
map.put(i, "deptName+" + i);
}
ForkJoinPool forkJoinPool = new ForkJoinPool(4);
QTaskUser task = new QTaskUser(0, list.size(), list,map);
ForkJoinTask<List<User>> submit = forkJoinPool.submit(task);
List<User> list2 = submit.get();
System.out.println(list2.size());
System.out.println(list2);
forkJoinPool.shutdown();
}
}
package com.example.demo.util;
import com.google.common.collect.Lists;
import lombok.SneakyThrows;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.ForkJoinTask;
import java.util.concurrent.RecursiveTask;
public class QTask extends RecursiveTask<List<String>> {
// 分片阈值
public static final int threshold = 3;
private int start;
private int end;
private List<Integer> list;
public QTask(int start, int end, List<Integer> list) {
this.start = start;
this.end = end;
this.list = list;
}
/**
*
*/
@Override
protected List<String> compute() {
List<String> result = Lists.newArrayList();
// 小于阈值直接执行
if (end - start <= threshold) {
for (int i = start; i < end; i++) {
result.add(i + "p");
}
} else {
// 递归拆解任务
int middle = (start + end) / 2;
QTask leftTask = new QTask(start, middle, list);
QTask rightTask = new QTask(middle, end, list);
invokeAll(leftTask, rightTask);
// 等待计算完成并返回计算结果。
List<String> list = leftTask.join();
List<String> list1 = rightTask.join();
result.addAll(list);
result.addAll(list1);
}
return result;
}
@SneakyThrows
public static void main(String[] args) {
List<Integer> list = new ArrayList<>();
for (int i = 0; i < 10000; i++) {
list.add(i);
}
ForkJoinPool forkJoinPool = new ForkJoinPool(4);
QTask task = new QTask(0, list.size(), list);
ForkJoinTask<List<String>> submit = forkJoinPool.submit(task);
List<String> list2 = submit.get();
System.out.println(list2.size());
System.out.println(list2);
forkJoinPool.shutdown();
}
}