JAVA ForkJoinTask 使用后记载
多线程这个东西并不陌生,可惜一直没有在实际中写过,最近突然要写一个多线程的功能,做完后感觉这个东西挺有意思的,后面对ForkJoinTask做了一些了解。
简单介绍
Fork/Join框架是Java 7提供的一个用于并行执行任务的框架,是一个把大任务分割成若干个小任务,最终汇总每个小任务结果后得到大任务结果的框架。Fork/Join框架要完成两件事情:
1.任务分割:首先Fork/Join框架需要把大的任务分割成足够小的子任务,如果子任务比较大的话还要对子任务进行继续分割
2.执行任务并合并结果:分割的子任务分别放到双端队列里,然后几个启动线程分别从双端队列里获取任务执行。子任务执行完的结果都放在另外一个队列里,启动一个线程从队列里取数据,然后合并这些数据。
要使用ForkJoin框架,必须首先创建一个ForkJoin任务。它提供在任务中执行fork()和join的操作机制,通常我们不直接继承ForkjoinTask类,只需要直接继承其子类。
1. RecursiveAction,用于没有返回结果的任务
2. RecursiveTask,用于有返回值的任务
task要通过ForkJoinPool来执行,分割的子任务也会添加到当前工作线程的双端队列中,进入队列的头部。当一个工作线程中没有任务时,会从其他工作线程的队列尾部获取一个任务。
ForkJoin框架使用了工作窃取的思想(work-stealing),算法从其他队列中窃取任务来执行。
ForkJoinPool:有三个方法来执行ForkJoinTask,分别为:execute(异步,不返回结果)/invoke(同步,返回结果)/submit(异步,返回结果)。
现在假如有一个List<Map<String,Object>> listMapDate的一个集合,我们需要将他写到一个txt文件里面,代码就可以这样编写:
public class ForkJoinWorkTask extends RecursiveTask<Integer> {
/**
* 阈值
*/
private static final int THRESHOLD = 2;
private List<Map<String, Object>> listMap;
public ForkJoinWorkTask(List<Map<String, Object>> listMap) {
this.listMap = listMap;
}
@Override
protected Integer compute() {
if (listMap.size() <= THRESHOLD) {
return this.computeDirectly();
} else {
//得到listMap的数量,如果要处理的数据大于2,则进行分组插入
int iListMapSize = listMap.size();
//第一个分组
ForkJoinWorkTask aForkJoinWorkTask = new ForkJoinWorkTask(listMap.subList(0, iListMapSize / 2));
//第二个分组
ForkJoinWorkTask bForkJoinWorkTask = new ForkJoinWorkTask(listMap.subList(iListMapSize / 2, iListMapSize));
//两个任务并发执行起来
invokeAll(aForkJoinWorkTask, bForkJoinWorkTask);
//两个分组的插入的行数加起来
return aForkJoinWorkTask.join() + bForkJoinWorkTask.join();
}
}
/**
* 业务处理逻辑
*/
private int computeDirectly() {
// try {
// Thread.sleep((long) (records.size() * 1000));
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
System.out.println("处理了:" + Arrays.toString(listMap.toArray()));
try {
// 相对路径,如果没有则要建立一个新的文件
File writeFile = new File("D:/pint_channel1.txt");
if (!writeFile.exists()) {
// 创建新文件,有同名的文件的话直接覆盖
writeFile.createNewFile();
}
BufferedWriter out = new BufferedWriter(new OutputStreamWriter(
new FileOutputStream(writeFile, true)));
try {
for (int i = 0; i < listMap.size(); i++) {
Map<String,Object> map = listMap.get(i);
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append(map.get("name")).append(",");
stringBuilder.append(map.get("orderid"));
// \r\n即为换行
out.write(stringBuilder.toString() + "\r\n");
}
out.flush(); // 把缓存区内容压入文件
} catch (Exception e) {
e.printStackTrace();
} finally {
out.close();
}
} catch (IOException e) {
e.printStackTrace();
}
return listMap.size();
}
}
编写ForkJoinTask任务的关键在于,在执行任务的compute()方法内部,先判断任务是不是足够小,如果足够小,就直接就直接调用业务处理的方法computeDirectly(),否则,把自身任务一拆为二,分别计算两个子任务,再调用业务处理的方法。
//第一个分组
ForkJoinWorkTask aForkJoinWorkTask = new ForkJoinWorkTask(listMap.subList(0, iListMapSize / 2));
//第二个分组
ForkJoinWorkTask bForkJoinWorkTask = new ForkJoinWorkTask(listMap.subList(iListMapSize / 2, iListMapSize));
//两个任务并发执行起来
invokeAll(aForkJoinWorkTask, bForkJoinWorkTask);
这边需要注意分裂的处理,这边有一个fork()的方法和invokeAll()的方法,这两个方法是有区别的。
下面打个比方 :
ForkJoin的工作模式就像这样:首先,工人甲被分配了400个房间的任务,他一看任务太多了自己一个人不行,所以先把400个房间拆成两个200,然后叫来乙,把其中一个200分给乙。
紧接着,甲和乙再发现200也是个大任务,于是甲继续把200分成两个100,并把其中一个100分给丙,类似的,乙会把其中一个100分给丁,这样,最终4个人每人分到100个房间,并发执行正好是1天。
invokeAll的N个任务中,其中N-1个任务会使用fork()交给其它线程执行,但它还会留一个任务自己执行,这样,就充分利用了线程池,保证没有空闲的不干活的线程。
如果换成 :
aForkJoinWorkTask.fork();
bForkJoinWorkTask.fork();
甲把400分成两个200后,这种写法相当于甲把一个200分给乙,把另一个200分给丙,然后,甲成了监工,不干活,等乙和丙干完了他直接汇报工作。乙和丙在把200分拆成两个100的过程中,他俩又成了监工,这样,本来只需要4个工人的活,现在需要7个工人才能1天内完成,其中有3个是不干活的。
最后写一个main方法进行测试,这个mian方法用的submit(异步,返回结果)
public static void main(String[] args) throws ExecutionException, InterruptedException {
List<Map<String, Object>> list = new ArrayList<Map<String, Object>>();
try {
for(int i = 0 ;i < 200000;i++) {
Map<String, Object> map = new HashMap<>();
map.put("name", "ForkJoinWorkTask : " + i);
map.put("orderid", "ForkJoinWorkTask : id" + i);
//获取其实内容
list.add(map);
}
} catch (Exception e) {
e.printStackTrace();
}
ForkJoinPool forkJoinPool = new ForkJoinPool(8);
ForkJoinWorkTask forkJoinWorkTask = new ForkJoinWorkTask(list);
long t1 = System.currentTimeMillis();
ForkJoinTask<Integer> reslut = forkJoinPool.submit(forkJoinWorkTask);
System.out.println(reslut.get());
long t2 = System.currentTimeMillis();
System.out.println(t2 - t1);
}
上面有些内容由别处参考而来,参考地址。
[1]: https://www.liaoxuefeng.com/article/1146802219354112