分治法 (Divide and Conquer)
- 基本思想:把一个规模大的问题划分为规模较小的子问题,然后分而治之,最后合并子问题的解得到原问题的解。
- 步骤:
- 分割原问题
- 求解子问题
- 并子问题的解为原问题的解
-
在分治法中,子问题一般是相互独立的,因此,经常通过递归调用算法来求解子问题。
一道数组求和:
public class SumRecursiveMT {
public static class RecursiveSumTask implements Callable<Long> {
public static final int SEQUENTIAL_CUTOFF = 10;
int lo;
int hi;
int[] arr; // arguments
ExecutorService executorService;
RecursiveSumTask( ExecutorService executorService, int[] a, int l, int h) {
this.executorService = executorService;
this.arr = a;
this.lo = l;
this.hi = h;
}
@Override
public Long call() throws Exception { // override
System.out.format("%s range [%d-%d] begin to compute %n",
Thread.currentThread().getName(), lo, hi);
long result = 0;
if (hi - lo <= SEQUENTIAL_CUTOFF) {
for (int i = lo; i < hi; i++)
result += arr[i];
System.out.format("%s range [%d-%d] begin to finished %n",
Thread.currentThread().getName(), lo, hi);
}
else {
RecursiveSumTask left = new RecursiveSumTask(executorService, arr, lo, (hi + lo) / 2);
RecursiveSumTask right = new RecursiveSumTask(executorService, arr, (hi + lo) / 2, hi);
Future<Long> lr = executorService.submit(left);
Future<Long> rr = executorService.submit(right);
result = lr.get() + rr.get();
System.out.format("%s range [%d-%d] finished to compute %n",
Thread.currentThread().getName(), lo, hi);
}
return result;
}
}
public static long sum(int[] arr) throws Exception {
int nofProcessors = Runtime.getRuntime().availableProcessors();
ExecutorService executorService = Executors.newFixedThreadPool(4);
// ExecutorService executorService = Executors.newCachedThreadPool();
RecursiveSumTask task = new RecursiveSumTask(executorService, arr, 0, arr.length);
long result = executorService.submit(task).get();
return result;
}
public static void main(String[] args) throws Exception {
int[] arr = Utils.buildRandomIntArray(50);
System.out.printf("The array length is: %d\n", arr.length);
long result = sum(arr);
System.out.printf("The result is: %d\n", result);
}
}
- 效果图如下:
- 但经过运行发现,程序到这里就进行不下去了
- 简单分析之后发现4个线程一直处于waiting状态,那他究竟在等待什么?因为线程池只有4个线程,而递归拆分50大小的数组需要1+2+4+8,一共15个线程,没有线程去完成接下来的任务。
- 就下图的情况,没有线程去执行接下来的程序,所以线程都结束不了,都处于等待状态。
- 当你不在限制它的线程数的时候
ExecutorService executorService = Executors.newCachedThreadPool();
The array length is: 50
pool-1-thread-1 range [0-50] begin to compute
pool-1-thread-2 range [0-25] begin to compute
pool-1-thread-3 range [25-50] begin to compute
pool-1-thread-4 range [0-12] begin to compute
pool-1-thread-7 range [37-50] begin to compute
pool-1-thread-5 range [12-25] begin to compute
pool-1-thread-6 range [25-37] begin to compute
pool-1-thread-8 range [37-43] begin to compute
pool-1-thread-8 range [37-43] begin to finished
pool-1-thread-10 range [0-6] begin to compute
pool-1-thread-10 range [0-6] begin to finished
pool-1-thread-9 range [43-50] begin to compute
pool-1-thread-9 range [43-50] begin to finished
pool-1-thread-7 range [37-50] finished to compute
pool-1-thread-11 range [12-18] begin to compute
pool-1-thread-11 range [12-18] begin to finished
pool-1-thread-12 range [6-12] begin to compute
pool-1-thread-12 range [6-12] begin to finished
pool-1-thread-13 range [18-25] begin to compute
pool-1-thread-13 range [18-25] begin to finished
pool-1-thread-4 range [0-12] finished to compute
pool-1-thread-14 range [25-31] begin to compute
pool-1-thread-14 range [25-31] begin to finished
pool-1-thread-5 range [12-25] finished to compute
pool-1-thread-2 range [0-25] finished to compute
pool-1-thread-15 range [31-37] begin to compute
pool-1-thread-15 range [31-37] begin to finished
pool-1-thread-6 range [25-37] finished to compute
pool-1-thread-3 range [25-50] finished to compute
pool-1-thread-1 range [0-50] finished to compute
The result is: 23980
- 按照预期执行成功,但随之出现了一个问题,如果我的数组很大很大,岂不是需要很多线程来参与。
- 10W大小的数组,参与的线程数已经上W了,再大一点不敢想象,这样搞还不如一个线程跑。
- 线程不是越多越好,拆分的话也不是越细越好。
pool-1-thread-15992 range [42802-42808] begin to finished
pool-1-thread-12490 range [42796-42802] begin to compute
pool-1-thread-12490 range [42796-42802] begin to finished
pool-1-thread-12491 range [41375-41381] begin to compute
pool-1-thread-12491 range [41375-41381] begin to finished
pool-1-thread-16079 range [41369-41375] begin to compute
pool-1-thread-16079 range [41369-41375] begin to finished
pool-1-thread-11157 range [65807-65820] finished to compute
pool-1-thread-10080 range [71239-71251] finished to compute
pool-1-thread-10082 range [71190-71202] finished to compute
pool-1-thread-8334 range [56469-56481] finished to compute
pool-1-thread-12919 range [8134-8140] begin to compute
pool-1-thread-3544 range [56469-56493] finished to compute
pool-1-thread-12230 range [8128-8134] begin to compute
pool-1-thread-12230 range [8128-8134] begin to finished
-
针对上述问题,在Java 1.7 引入了一种新的并发框架—— Fork/Join Framework
-
主要用于实现“分而治之”的算法,特别是分治之后递归调用的函数
-
提供了的一个用于并行执行任务的框架, 是一个把大任务分割成若干个小任务,最终汇总每个小任务结果后得到大任务结果的框架
-
与ThreadPool共存,并不是要替换ThreadPool
- ForkJoin实现数组求和例子