Java代码优化实战 - 分而治之
我们介绍了请求合并的代码优化方案,它能够解决大量请求造成的数据库压力过大的情况,下面我们再来看看另一种情况下的解决方案。
其实在学数据结构和算法的时候,大家应该都接触过分而治之的思想,其实说白了就是递归调用本函数的一个过程,在这个过程中,不断把任务变小,简化计算的流程。这种思想,在进行系统架构的时候同样适用。如果一个请求要访问大量的数据,那么我们就可以将这个任务拆分分别执行,最终再将执行结果返回给客户端。
这里就要引入JDK 1.7后提供的一个多线程执行框架Fork/Join
,它能够把一个大任务分割成若干个小任务,最终汇总每个小任务结果后得到大任务结果。
ForkJoin
框架为我们提供了RecursiveAction
和RecursiveTask
来创建ForkJoin
的任务,简单来说:
Recursiveaction
: 用于创建没有返回值的任务RecursiveTask
:用于创建有返回值的任务
举个例子,还是用上一小节中我们的数据,现在数据库中存储了id从0到999的一千件商品,我们要对其总值进行求和(别问为什么不直接用sum()
函数,举个例子而已)。
@ResponseBody @RequestMapping("/single") public int single() { long startTime = System.currentTimeMillis(); int sum = 0; for (int i = 0; i < 1000; i++) { sum += itemService.queryByCode(i + "").getPrice(); } System.out.println(sum); long endTime = System.currentTimeMillis(); System.out.println("程序运行时间:" + (endTime - startTime) + "ms"); return sum; } 复制代码
看一下程序运行时间,5235毫秒:
使用ForkJoin
对任务进行划分:
public class ForkJoinTask extends RecursiveTask<Integer> { private int arr[]; private int start; private int end; private static final int MAX = 50; public ForkJoinTask(int[] arr, int start, int end){ this.arr=arr; this.start = start; this.end = end; } @Override protected Integer compute() { int sum=0; if((end - start) < MAX) { //直接做业务工作 for (int i = start; i < end; i++) { sum += arr[i]; } return sum; } else{ //继续拆分 int middle = (start + end) / 2; ForkJoinTask left=new ForkJoinTask(arr, start, middle); ForkJoinTask right=new ForkJoinTask(arr, middle, end); left.fork(); right.fork(); return left.join() + right.join(); } } } 复制代码
再运行测试:
@ResponseBody @RequestMapping("/fork") public int forkJoin() { long startTime = System.currentTimeMillis(); int arr[] = new int[1000]; for (int i = 0; i < 1000; i++) { arr[i]=i; } ForkJoinPool pool=new ForkJoinPool(); ForkJoinTask task=new ForkJoinTask(arr,0,arr.length); Integer sum = pool.invoke(task); System.out.println(sum); long endTime = System.currentTimeMillis(); System.out.println("程序运行时间:" + (endTime - startTime) + "ms"); return sum; } 复制代码
再看一下程序运行时间,只有6毫秒:
是不是觉得快了很多,直接将运行速度提升了非常多!其实ForkJoin
运行速度快的原因还有一个黑科技,那就是当一个线程在完成自己的任务队列的处理任务后,会帮助其他线程完成任务,完成后再放回其他队列,这也被称为工作窃取。
如上图所示,线程1在完成自己的任务后,发现线程2还有任务没有完成,这时它会去取到线程2没有完成的任务,做完后再把结果放回线程2。
除此之外,我们还可以通过增加线程数量进一步加快运行速度,线程数量的选择可以根据具体业务环境进行配置优化。
ForkJoinPool pool=new ForkJoinPool(Runtime.getRuntime().availableProcessors()*4); 复制代码
总结:
与上一篇文章一起,分别从请求合并和分而治之两种角度介绍了系统的优化,可以看出,在平常的工作中,代码优化这一条路还有很长要走。文中的代码大家可以从我的github获取。
作者:Hydra
链接:https://juejin.cn/post/7018091060718993439