阅读 82

Java代码优化实战 - 分而治之

我们介绍了请求合并的代码优化方案,它能够解决大量请求造成的数据库压力过大的情况,下面我们再来看看另一种情况下的解决方案。

其实在学数据结构和算法的时候,大家应该都接触过分而治之的思想,其实说白了就是递归调用本函数的一个过程,在这个过程中,不断把任务变小,简化计算的流程。这种思想,在进行系统架构的时候同样适用。如果一个请求要访问大量的数据,那么我们就可以将这个任务拆分分别执行,最终再将执行结果返回给客户端。

这里就要引入JDK 1.7后提供的一个多线程执行框架Fork/Join,它能够把一个大任务分割成若干个小任务,最终汇总每个小任务结果后得到大任务结果。

ForkJoin框架为我们提供了RecursiveActionRecursiveTask来创建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


文章分类
后端
版权声明:本站是系统测试站点,无实际运营。本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 XXXXXXo@163.com 举报,一经查实,本站将立刻删除。
相关推荐