什么是Fork\Join框架
从JDK1.7开始,Java提供Fork/Join框架用于并行执行任务。它的思想就是讲一个大任务分割成若干小任务,最终汇总每个小任务的结果得到这个大任务的结果。如下图:
如何实现Fork\Join
java 的ForkJoinTask 抽象类中提供compute方法给我们实现这种思想。java又提供两个抽象类继承ForkJoinTask ,分别是:RecursiveTask有返回值,RecursiveAction无返回值。
java7代码实例
1、定义一个实体类
package com.test;
import lombok.Builder;
import lombok.Data;
import java.io.Serializable;
@Data
@Builder
public class Student implements Serializable {
private static final long serialVersionUID = -6467381368884568257L;
private int code;
private String name;
}
2、编写Fork\Join框架和业务代码
package com.test;
import lombok.AllArgsConstructor;
import lombok.NoArgsConstructor;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.RecursiveTask;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@NoArgsConstructor
@AllArgsConstructor
public class ForkJoinPoolList extends RecursiveTask<List<String>> {
private static final long serialVersionUID = -4154344699699776282L;
private List<Student> lists;
private int start;
private int end;
private int max;
@Override
protected List<String> compute() {
if((end - start) < max) {
// 切分到可以执行状态
System.out.println("当前线程为:" + Thread.currentThread().getName() + ";开始:" + start + ";结束:" + end);
List<String> results = new ArrayList<>();
for(int i = start - 1; i < end; i++) {
results.add(lists.get(i).getCode() + "\t" + lists.get(i).getName());
}
return results;
} else {
// 任务分解
int average = (start + end) / 2;
ForkJoinPoolList task1 = new ForkJoinPoolList(lists, start, average, max);
ForkJoinPoolList task2 = new ForkJoinPoolList(lists, average + 1, end, max);
task1.fork();
task2.fork();
return Stream.concat(task1.join().stream(), task2.join().stream()).collect(Collectors.toList());
}
}
}
3、调用Fork\Join
package com.test;
import org.junit.jupiter.api.Test;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.ForkJoinTask;
public class ForkJoinTest {
@Test
public void testForkJoinList() throws ExecutionException, InterruptedException {
List<Student> students = new ArrayList<>();
for(int i = 1; i <= 1000000; i++) {
students.add(Student.builder().code(i).name(i % 2 == 0 ? "张三" : "李四").build());
}
// Fork/Join框架的线程池
ForkJoinPool pool = new ForkJoinPool();
ForkJoinTask<List<String>> taskFuture = pool.submit(new ForkJoinPoolList(students, 1, students.size(), 1000));
List<String> results = taskFuture.get();
}
}
本例子实现了将一个大的List处理,转换成多个小list并且分发给多个线程进行操作,同时将多个小list执行的结果合并起来的过程。
java8代码实现
package com.test;
import org.junit.jupiter.api.Test;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
public class ForkJoinTest {
@Test
public void testParallelStream() {
List<Student> students = new ArrayList<>();
for(int i = 1; i <= 1000000; i++) {
students.add(Student.builder().code(i).name(i % 2 == 0 ? "张三" : "李四").build());
}
List<String> results = students.parallelStream().map(stu -> {
System.out.println("当前线程为:" + Thread.currentThread().getName());
return stu.getCode() + "\t" + stu.getName();
}).collect(Collectors.toList());
}
}
其中parallelStream是java8提供的并行流,内部还是通过Fork\Join框架来实现。
使用场景
虽然Fork\Join可以高效并行处理,但是如果使用不当,不但不能高效,反而会降低代码效率。
就比如上面的实例,由于业务的复杂度不够,使用框架反而会降低性能,使用如下单线程执行还会有更快的效率。
@Test
public void testList() {
List<Student> students = new ArrayList<>();
for(int i = 1; i <= 1000000; i++) {
students.add(Student.builder().code(i).name(i % 2 == 0 ? "张三" : "李四").build());
}
List<String> results = students.stream().map(stu -> stu.getCode() + "\t" + stu.getName()).collect(Collectors.toList());
}
这是因为初始化fork/join框架的时间会远多于执行任务所需时间,也就导致了效率的降低。
所以要使用并行执行,需要满足一下条件:
- 计算的规模要够大,确保在线程中运算的时间多过分配线程的开销
- 并行的数据必须是无序并且无状态的,前者会影响速度,后者会影响结果正确性(状态的意思是共享的意思,如共享的对象,变量等)
根据附录doug Lee的说明,任务数量*执行方法的行数>=10000或者执行的是消耗大量时间操作(如io/数据库)才有必要使用