分支合并,分而治之。
- Fork/Join 框架是一个实现了 ExecutorService 接口的多线程处理器,它专为那些可以通过递归分解成更细小的任务而设计,最大化的利用多核处理器来提高应用程序的性能。与其他 ExecutorService 相关的实现相同的是,Fork/Join 框架会将任务分配给线程池中的线程,而与之不同的是,Fork/Join 框架在执行任务时使用了工作窃取算法。
- Fork/Join框架:在必要的情况下,将一个大任务,拆分成若干个小任务(拆到不可再拆时),再将一个个的小任务运算的结果进行合并。
- 大任务:任务递归分配成若干小任务。
- 小任务:并行求值后结果合并。
- Fork/Join框架与线程池的区别:
- Fork/Join框架采用工作窃取模式(Work-Stealing)。
- 相对于一般的线程池实现,Fork/Join框架的优势体现在对其中包含的任务的处理方式上。在一般的线程池中,如果一个线程正在执行的任务由于某些原因无法继续运行,那么该线程会处于等待状态。而在Fork/Join框架实现中,如果某个子问题由于等待另外一个子问题的完成而无法继续运行,那么处理该子问题的线程会主动寻找其他尚未运行的子问题来执行。这种方式减少了线程的等待时间,提高了性能。
- 工作窃取算法:将一个大任务分割为若干互不依赖的子任务,为了减少线程间的竞争,把这些子任务分别放到不同的队列里,并为每个队列创建一个单独的线程来执行队列里的任务,线程和队列一一对应。但是有的线程会先把自己对应的队列里的任务干完,而其他线程对应的队列里还有任务等待处理,干完活的线程就会去其他线程的队列里窃取一个任务来执行,此时他们会访问同一个队列,为了减少窃取任务线程和被窃取任务线程之间的竞争,通常会使用双端队列,被窃取任务线程从双端队列的头部取出任务执行,窃取任务线程从双端队列的尾部取出任务执行。当一个线程在窃取任务时要是没有其他可用的任务了,这个线程会进入阻塞状态以等待再次“工作”。
- 工作窃取算法的有点:充分利用线程进行并行计算,减少线程间的竞争。
- 工作窃取算法的缺点:某些情况下还是存在线程竞争,比如双端队列里只有一个任务时。该算法会消耗更多的系统资源,比如创建多个线程和多个双端队列。
- ForkJoinPool 由 ForkJoinTask 数组和 ForkJoinWorkerThread 数组组成,ForkJoinTask 数组负责将存放程序提交给 ForkJoinPool 的任务,ForkJoinWorkerThread 数组负责执行这些任务。
- ForkJoinTask 是一个类似普通线程的实体,但是比普通线程轻量得多。ForkJoinWorkerThread 是执行 ForkJoinTask 的专有线程,由 ForkJoinPool 管理。
- ForkJoinPool 是用于执行 ForkJoinTask 任务的执行(线程)池。ForkJoinPool 管理着执行池中的线程和任务队列,执行池是否还接受任务、显示线程的运行状态等也是它的功能。
- ForkJoinPool 与传统线程池最显著的区别就是它维护了一个工作队列数组
volatile WorkQueue[] workQueues
,ForkJoinPool 中的每个工作线程都维护着一个工作队列。
package com.java.forkjoinpool;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.ForkJoinTask;
import java.util.concurrent.RecursiveTask;
/**
* @author rrqstart
* @Description 计算0~100所有数的和
* public class ForkJoinPool extends AbstractExecutorService {//......}
* public abstract class ForkJoinTask<V> implements Future<V>, Serializable {//......}
* public abstract class RecursiveTask<V> extends ForkJoinTask<V> {//......}
*/
public class ForkJoinPoolTest {
public static void main(String[] args) {
ForkJoinPool pool = null;
try {
MyTask myTask = new MyTask(0, 100); //生成一个计算任务
pool = new ForkJoinPool();
ForkJoinTask<Integer> forkJoinTask = pool.submit(myTask); //执行一个任务
Integer result = forkJoinTask.get();
System.out.println("result = " + result); //result = 5050
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
} finally {
pool.shutdown();
}
}
}
class MyTask extends RecursiveTask<Integer> {
private static final Integer ADJUST_VALUE = 10; //阈值
private int begin;
private int end;
private int result;
public MyTask(int begin, int end) {
this.begin = begin;
this.end = end;
}
@Override
protected Integer compute() {
//如果任务足够小,就直接计算任务
if ((end - begin) <= ADJUST_VALUE) {
for (int i = begin; i <= end; i++) {
result += i;
}
} else {
//如果任务大于阈值,就分割成两个子任务
int middle = (begin + end) / 2;
MyTask task1 = new MyTask(begin, middle);
MyTask task2 = new MyTask(middle + 1, end);
//fork()方法:使用线程池中的空闲线程异步提交任务,把任务推入当前工作线程的工作队列里.
//执行子任务:子任务调用fork方法时会再次进入compute方法
task1.fork();
task2.fork();
//join()方法:等待处理任务的线程处理完毕,获得返回值.
//Thread.join()会使线程阻塞,而ForkJoinPool.join()会使线程免于阻塞.
//join方法会等待子任务执行完并得到其结果
result = task1.join() + task2.join();
}
return result;
}
}