Fork/Join框架
Fork/Join框架是Jdk1.7提供的一个用于并发执行任务的框架。
它将一个大任务分割为若干个小任务,最终汇总每个小任务结果后得到大任务结果的框架。
Fork就是将一个大任务切分为若干个子任务并行的执行;
Join就是合并这些子任务的执行结果,最后得到这个大任务的结果。
(如果你了解过Hadoop的MapReduce的话,会发现,和这个玩意很像耶...)
Fork/Join 实例Demo
public class Task extends RecursiveTask<Integer> {
private static final long serialVersionUID = 1L;
private List<Integer> numberList;
private int start;
private int end;
private int THRESHOID = 10;
public Task(List<Integer> numberList) {
this.numberList = numberList;
start = 0;
end = this.numberList.size() - 1;
}
public Task(List<Integer> numberList, int start, int end) {
this.numberList = numberList;
this.start = start;
this.end = end;
}
@Override
protected Integer compute() {
if (end - start <= THRESHOID) {
return sum();
} else {
int pivot = (end + start) / 2;
Task task1 = new Task(numberList, start, pivot);
Task task2 = new Task(numberList, pivot + 1, end);
task1.fork();
task2.fork();
return task1.join() + task2.join();
}
}
private Integer sum() {
Integer sum = 0;
for (int i = start; i <= end; i++) {
sum += this.numberList.get(i);
}
return sum;
}
}
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.Future;
public class Main {
public static void main(String[] args) throws Exception {
ForkJoinPool pool = new ForkJoinPool();
Task task = new Task(getData(100));
Future<Integer> result = pool.submit(task);
System.out.println(result.get());
}
private static List<Integer> getData(int len) {
List<Integer> list = new ArrayList<Integer>();
for (int i = 1; i <= len; i++) {
list.add(i);
}
return list;
}
}
输出结果:
5050
public class CountTask extends RecursiveTask<Integer> {
private static final long serialVersionUID = 1L;
private static final int THRESHOLD = 2;// 阀值
private int start;
private int end;
public CountTask(int start, int end) {
this.start = start;
this.end = end;
}
@Override
protected Integer compute() {
int sum = 0;
boolean canCompute = (end - start) <= THRESHOLD;
if (canCompute) {// 如果任务足够小就计算任务
for (int i = start; i <= end; i++) {
sum += i;
}
} else {// 如果任务大于阀值,就分裂成两个子任务计算
int middle = (start + end) / 2;
CountTask leftTask = new CountTask(start, middle);
CountTask rightTask = new CountTask(middle + 1, end);
// 执行子任务
leftTask.fork();
rightTask.fork();
// 等待子任务执行完成,并得到其结果
int leftResult = leftTask.join();
int rightRestul = rightTask.join();
// 合并子任务结果
sum = leftResult + rightRestul;
}
return sum;
}
}
public class TestMain {
public static void main(String[] args) throws Exception {
ForkJoinPool pool = new ForkJoinPool();
CountTask countTask = new CountTask(1, 9);
Future<Integer> result = pool.submit(countTask);
System.out.println(result.get());
}
}
输出结果:
45
工作窃取算法是指:
某个线程从其他队列中窃取任务来执行。
举个栗子:
假设我们有一个比较大得到任务,把这个任务分割为若干不依赖的子任务,为了减少线程间的竞争,把这些子任务分别放到不同的任务队列中,并为每个队列创建一个单独的线程来执行队列里的任务,线程队列一一对应。
有的线程会先把自己队列里的任务干完,而其他线程对应的队列中还有任务等待处理。
干完活的线程与其等着,不如去帮其他线程干活,于是它就去其他线程的任务队列中窃取一个任务线程来执行。
为了减少窃取任务时,线程之间的竞争,通常采用双端队列,被窃取任务线程永远从队列的头部拿任务执行,窃取任务线程永远从双端队列的尾部拿任务执行。
工作窃取算法的优点:充分利用线程进行并行计算,减少了线程间的竞争。
工作窃取算法的缺点:在某些情况下还是存在竞争的,比如双端队列中只有一个任务时。
并且该算法会消耗更多的系统资源,比如创建多个线程和多个双端队列。
Fork/Join框架的设计
1. 分割任务
首先我们需要有一个fork类来把大任务分割成子任务,有可能子任务还是很大,所以需要不停的分割,
直到分割出的子任务足够小。
2. 执行任务并合并结果
分割的子任务分别放在双端队列中,然后启动几个线程分别从双端队列中获取任务执行。
子任务执行完的结果都同一放在一个队列中,启动一个线程从这些队列中拿结果数据,然后合并这些数据。
Fork/Join使用两个类来完成以上两件事。
a. ForkJoinTask:
1) 使用ForkJoin框架,首先必须创建一个ForkJoin任务。它提供任务中执行fork()和join()操作的机制。
2) 通常情况下,不需要直接继承ForkJoinTask类,只需要继承它的两个子类:
RecursiveAction:用于没有返回结果的任务
RecutsiveTask:用于有返回结果的任务
b. ForkJoinPool:
ForkJoinTask需要通过ForkJoinPool来执行。
Fork/Join框架暂时先说这么多
有空的话后续在《JAVA并发编程核心方法与框架》系列笔记中详述