java中常见的六种线程池详解( 四 )

, 即第0项为0 ,第一项为1,则第二项为 0+1=1,以此类推 。我们最初的解决方法就是使用递归来解决,如下计算第n项的数值:private int num(int num){if (num <= 1){return num;}num = num(num-1) + num(num -2);return num;}

  • 从上面简单代码中可以看到,当 n<=1 时返回 n , 如果n>1 则计算前一项的值f1,在计算前两项的值f2, 再将两者相加得到结果,这就是典型的递归问题,也是对应我们的ForkJoin 的工作模式,如下所示,根节点产生子任务,子任务再次衍生出子子任务,到最后在进行整合汇聚,得到结果 。

java中常见的六种线程池详解

文章插图
  • 我们通过 ForkJoinPool 来实现斐波那契数列的计算,如下展示:
/** * @url: i-code.online * @author: AnonyStar * @time: 2020/11/2 10:01 */public class ForkJoinApp3 {public static void main(String[] args) throws ExecutionException, InterruptedException {ForkJoinPool pool = new ForkJoinPool();//计算第二是项的数值final ForkJoinTask<Integer> submit = pool.submit(new Fibonacci(20));// 获取结果,这里获取的就是异步任务的最终结果System.out.println(submit.get());}}class Fibonacci extends RecursiveTask<Integer>{int num;public Fibonacci(int num){this.num = num;}@Overrideprotected Integer compute() {if (num <= 1) return num;//创建子任务Fibonacci subTask1 = new Fibonacci(num - 1);Fibonacci subTask2 = new Fibonacci(num - 2);// 执行子任务subTask1.fork();subTask2.fork();//获取前两项的结果来计算和return subTask1.join()+subTask2.join();}}
  • 通过 ForkJoinPool 可以极大的发挥多核处理器的优势,尤其非常适合用于递归的场景,例如树的遍历、最优路径搜索等场景 。
  • 上面说的是ForkJoinPool 的使用上的,下面我们来说一下其内部的构造,对于我们前面说的几种线程池来说,它们都是里面只有一个队列,所有的线程共享一个 。但是在ForkJoinPool 中,其内部有一个共享的任务队列,除此之外每个线程都有一个对应的双端队列Deque , 当一个线程中任务被Fork 分裂了,那么分裂出来的子任务就会放入到对应的线程自己的Deque中,而不是放入公共队列 。这样对于每个线程来说成本会降低很多,可以直接从自己线程的队列中获取任务而不需要去公共队列中争夺,有效的减少了线程间的资源竞争和切换 。

java中常见的六种线程池详解

文章插图
  • 有一种情况,当线程有多个如t1,t2,t3...,在某一段时间线程 t1 的任务特别繁重,分裂了数十个子任务,但是线程 t0 此时却无事可做,它自己的 deque队列为空,这时为了提高效率,t0 就会想办法帮助t1 执行任务,这就是“work-stealing”的含义 。
  • 双端队列 deque中,线程t1 获取任务的逻辑是后进先出,也就是LIFO(Last In Frist Out),而线程t0在“steal”偷线程 t1 的 deque中的任务的逻辑是先进先出,也就是FIFO(Fast In Frist Out),如图所示,图中很好的描述了两个线程使用双端队列分别获取任务的情景 。你可以看到,使用 “work-stealing” 算法和双端队列很好地平衡了各线程的负载 。

java中常见的六种线程池详解

文章插图
本文由AnonyStar 发布,可转载但需声明原文出处 。
欢迎关注微信公账号 :云栖简码 获取更多优质文章
更多文章关注笔者博客 :云栖简码 i-code.online




推荐阅读