如果现在要你编写计算 1! + 2! + 3! + 4! + … + 20! 的代码,你会怎么做?
最自然的想法是从左边依次向右计算,不断迭代结果。毕竟对于人来说,一心二用还是比较难的,在同一时间只做一件事情是常见的场景。
用代码来实现的话,示例如下:
public class factorial {
public static int compute(int n) {
int result = 0;
for (int i = 1; i <= n; i++) {
int count = i;
int temp = 1;
while (count > 0) {
temp *= count;
count--;
}
result += temp;
}
return result;
}
public static void main(String[] args) {
System.out.println(compute(20));
}
}
Fork/Join 并行计算
现代的CPU一般都是双核以上,如果支持超线程的话,线程数一般都是4个以上。
每一个线程就相当于一个人,能独立执行任务。如何让多个线程同时工作,加快任务执行执行速度,充分利用CPU性能呢?
JDK 7 中引入的 Fork/Join 并行计算框架可以较好的解决这个问题,它类似于单机版的 MapReduce,都采用了分而治之的思想。
Fork 把一个任务切分为若干子任务并行执行,Join 用来合并这些子任务的执行结果,最终得到任务结果。子任务被分配到不同的核上执行时,效率最高。
Fork/Join 的运行流程图如下:
Fork/Join 使用两个类来完成 Fork 与 Join 两件事情:
- ForkJoinTask:通过 fork() 方法执行子任务。通常情况下不直接继承 ForkJoinTask 类,针对不同任务,选择 ForkJoinTask 的不同子类来完成任务:
- RecursiveAction:用于没有返回结果的任务,
- RecursiveTask :用于有返回结果的任务,通过 join() 方法获取
- ForkJoinPool :通过 submit() 方法执行 ForkJoinTask 任务,并实现了工作窃取算法
- public ForkJoinPool(int parallelism):创建一个包含 parallelism 个并行线程的 ForkJoinPool
- public ForkJoinPool() :以 Runtime.getRuntime().availableProcessors() 的返回值作为 parallelism 创建 ForkJoinPool
工作窃取算法
工作窃取使得先完成工作的线程可以帮助其它繁忙的线程,让任务更快的得以完成。对于上述例子来说,1! + 2! + 3! + … + 10!就比11! + 12! + 13! + … + 20!完成的更快(当数据量更大时,差异更明显)。
每个工作线程都有自己的双端工作队列。采用双端队列主要是为了减少争用和方便窃取。因为工作线程会从自己的队列头部取任务执行,而其它空闲线程会从工作线程队列尾部取任务执行。
工作窃取算法的优点是充分利用线程进行并行计算,减少了线程间的竞争;缺点是在某些情况下仍然存在竞争(双端队列里只有一个任务时),并且消耗了更多的系统资源(创建了多个线程和多个双端队列)。
对1! + 2! + 3! + 4! + … + 20!通过 Fork/Join 并行任务框架求解,示例如下:
public class ForkJoinDemo extends RecursiveTask{
private final int THRESHOLD = 10;
private int start;
private int end;
public ForkJoinDemo(int start, int end) {
this.start = start;
this.end = end;
}
@Override
protected Object compute() {
if (end - start < THRESHOLD) {
return factorial(start, end);
}
System.out.print("采用Fork/Join求解:");
int middle = end + start >>> 1;
ForkJoinDemo left = new ForkJoinDemo(start, middle);
ForkJoinDemo right = new ForkJoinDemo(middle + 1, end);
left.fork();
right.fork();
return (int)left.join() + (int)right.join();
}
public static int factorial(int start, int end) {
int result = 0;
for (int i = start; i <= end; i++) {
int count = i;
int temp = 1;
while (count > 0) {
temp *= count;
count--;
}
result += temp;
}
return result;
}
public static void main(String[] args) {
ForkJoinPool pool = new ForkJoinPool();
Future<Integer> result = pool.submit(new ForkJoinDemo(1,20));
try {
System.out.println(result.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
输出结果:
采用Fork/Join求解:268040729