GVKun编程网logo

大家 java 面试中会问 ForkjoinPool 的知识点吗(java面试会问到的问题)

1

本文将介绍大家java面试中会问ForkjoinPool的知识点吗的详细情况,特别是关于java面试会问到的问题的相关信息。我们将通过案例分析、数据研究等多种方式,帮助您更全面地了解这个主题,同时也将

本文将介绍大家 java 面试中会问 ForkjoinPool 的知识点吗的详细情况,特别是关于java面试会问到的问题的相关信息。我们将通过案例分析、数据研究等多种方式,帮助您更全面地了解这个主题,同时也将涉及一些关于13、JUC--ForkJoinPool 分支/合并框架 工作窃取、Error:java: Internal compiler error: java.lang.Exception: java.lang.NoClassDefFoundError 解决、ForkJoinPool、ForkJoinPool 分支 / 合并框架工程使用的工作窃取的知识。

本文目录一览:

大家 java 面试中会问 ForkjoinPool 的知识点吗(java面试会问到的问题)

大家 java 面试中会问 ForkjoinPool 的知识点吗(java面试会问到的问题)

如题。网上看过的面试经验和题目都很少提到 ForkjoinPool 的,是面试官不关心么?还是有其他什么原因

13、JUC--ForkJoinPool 分支/合并框架 工作窃取

13、JUC--ForkJoinPool 分支/合并框架 工作窃取

ForkJoinPool 分支/合并框架 工作窃取

Fork/Join 框架

Fork/Join 框架:就是在必要的情况下,将一个大任务,进行拆分(fork)成
若干个小任务(拆到不可再拆时),再将一个个的小任务运算的结果进
行 join 汇总

 

Fork/Join 框架与线程池的区别

 采用 “工作窃取”模式(work-stealing):
  当执行新的任务时它可以将其拆分分成更小的任务执行,并将小任务加
  到线程队列中,然后再从一个随机线程的队列中偷一个并把它放在自己的队
  列中。


 相对于一般的线程池实现,fork/join框架的优势体现在对其中包含的任务
  的处理方式上.在一般的线程池中,如果一个线程正在执行的任务由于某些
  原因无法继续运行,那么该线程会处于等待状态。而在fork/join框架实现中,
  如果某个子问题由于等待另外一个子问题的完成而无法继续运行。那么处理
  该子问题的线程会主动寻找其他尚未运行的子问题来执行.这种方式减少了
  线程的等待时间,提高了性能

 

 

代码实例:

class ForkJoinSum extends RecursiveTask<Long>{

    private static final long serialVersionUID = 1L;

    private long start;
    private long end;
//临界值-拆分到不可再拆分 private static final long THURSHOLD = 0L; public ForkJoinSum(long start,long end){ this.start = start; this.end=end; } @Override protected Long compute() { long length = end-start; //进行判断是否到达临界值 if(length <= THURSHOLD){ long sum = 0; for(long i = start; i<=end;i++){ sum = sum +i; } return sum; }else{ //原理: /* * 计算数值之和,首先进行对半处理 * 前一半进行和运算 * 后一半进行和运算 */ long middle = (start +end )/2; ForkJoinSum left = new ForkJoinSum(start, middle); left.fork();//进行拆分,同时压入线程队列 ForkJoinSum right = new ForkJoinSum(middle + 1, end); right.fork(); return left.join() + right.join(); } } }

首先需要继承RecursiveTask这个类,并且重写compute()方法

同时可以根据开发中的实际需要进行临界值的设定

拆分可以拆为多个,根基开发中的需要进行拆分

使用fork()方法进行拆分

使用join()方法进行合并

public static void main(String[] args) {
        
        ForkJoinPool pool = new ForkJoinPool();
        
        ForkJoinTask<Long> task =  new ForkJoinSum(0L,100000000L);
        
        Long sum = pool.invoke(task);
        
        System.out.println(sum);
    }

 

Error:java: Internal compiler error: java.lang.Exception: java.lang.NoClassDefFoundError 解决

Error:java: Internal compiler error: java.lang.Exception: java.lang.NoClassDefFoundError 解决

Error:java: Internal compiler error: java.lang.Exception: java.lang.NoClassDefFoundError 解决

IDEA 发现一个奇怪的错误
今天用 IDEA2018.1 运行 SpringBoot 项目报错如下:
Error:java: Internal compiler error: java.lang.Exception: java.lang.NoClassDefFoundError: org/springframework/boot/configurationprocessor/metadata/ConfigurationMetadata at org.eclipse.jdt.internal.compiler.apt.dispatch.RoundDispatcher.handleProcessor(RoundDispatcher.java:169)
解决方案:
分下看这个跟 Eclipse 有关,可是这个不是 Eclipse 项目(没有 Eclipse 的 Workspeace 信息),后来查找发现跟 Eclipse 有关设置就是之前自己设置锅编译器,改过来问题得到解决。解决步骤:IDEA 中 File-->settings-->Bulid Execution Deloyment-->Complier-->Java Complier 中的 user complier 有原来的 Eclipse 改为 javac 即可

ForkJoinPool

ForkJoinPool

  • fork():开启一个新线程(或是重用线程池内的空闲线程),将任务交给该线程处理。
  • join():等待该任务的处理线程处理完毕,获得返回值。

ForkJoinPool 的每个工作线程都维护着一个工作队列WorkQueue),这是一个双端队列(Deque),里面存放的对象是任务ForkJoinTask)。

每个工作线程在运行中产生新的任务(通常是因为调用了 fork())时,会放入工作队列的队尾,并且工作线程在处理自己的工作队列时,使用的是 LIFO 方式,也就是说每次从队尾取出任务来执行。

每个工作线程在处理自己的工作队列同时,会尝试窃取一个任务(或是来自于刚刚提交到 pool 的任务,或是来自于其他工作线程的工作队列),窃取的任务位于其他线程的工作队列的队首,也就是说工作线程在窃取其他工作线程的任务时,使用的是 FIFO 方式。

在遇到 join() 时,如果需要 join 的任务尚未完成,则会先处理其他任务,并等待其完成。

在既没有自己的任务,也没有可以窃取的任务时,进入休眠。

 

fork

fork() 做的工作只有一件事,既是把任务推入当前工作线程的工作队列里。可以参看以下的源代码:

join

join() 的工作则复杂得多,也是 join() 可以使得线程免于被阻塞的原因 —— 不像同名的 Thread.join()

 

1. 检查调用 join() 的线程是否是 ForkJoinThread 线程。如果不是(例如 main 线程),则阻塞当前线程,等待任务完成。如果是,则不阻塞。

2. 查看任务的完成状态,如果已经完成,直接返回结果。

3. 如果任务尚未完成,但处于自己的工作队列内,则完成它。

4. 如果任务已经被其他的工作线程偷走,则窃取这个小偷的工作队列内的任务(以 FIFO 方式),执行,以期帮助它早日完成欲 join 的任务。

5. 如果偷走任务的小偷也已经把自己的任务全部做完,正在等待需要 join 的任务时,则找到小偷的小偷,帮助它完成它的任务。

6. 递归地执行第 5 步。

以上就是 fork()join() 的原理,这可以解释 ForkJoinPool 在递归过程中的执行逻辑,但还有一个问题

最初的任务是 push 到哪个线程的工作队列里的?

这就涉及到 submit() 函数的实现方法了

其实除了前面介绍过的每个工作线程自己拥有的工作队列以外,ForkJoinPool 自身也拥有工作队列,这些工作队列的作用是用来接收由外部线程(非 ForkJoinThread 线程)提交过来的任务,而这些工作队列被称为 submitting queue

submit()fork() 其实没有本质区别,只是提交对象变成了 submitting queue 而已(还有一些同步,初始化的操作)。submitting queue 和其他 work queue 一样,是工作线程” 窃取 “的对象,因此当其中的任务被一个工作线程成功窃取时,就意味着提交的任务真正开始进入执行阶段。

 

ForkJoinPool 分支 / 合并框架工程使用的工作窃取

ForkJoinPool 分支 / 合并框架工程使用的工作窃取

ForkJoinPool 分支 / 合并框架

在必要的情况下,讲一个大任务,进行拆分(fork)成若干个小任务(拆到不可拆为止),再将一个个小的任务运算的结果进行 join 汇总。

工作窃取的背景

分支 / 合并框架,里面提到了 ForkJoinSumCalculator 会将一个任务分成很多个子任务,一般来说分出大量的子任务是个好的选择。因为在理想的情况下,划分并行任务时,应该要让每个任务都用完全相同的时间完成,让所有的 CPU 内核都同样繁忙。但是实际中,每个子任务所花费的时间可能天差地别,要么因为划分策略低,要么因为不可预知的原因,比如磁盘访问慢,或者需要和外部服务协调执行。

工作窃取的使用

针对多个线程分配相同多子任务的时候,会出现不同线程完成所有任务的时间有快有慢的情况,分支 / 合并框架工程使用了工作窃取的技术来解决这个问题。在实际应用中,这些子任务被差不多的分配到 ForkJoinSumCalculator 中的所有线程上,每个线程都为分配给他的任务保存一个双向的链式队列,每完成一个任务,就会队列头上取出下一个任务开始执行。因为上面所述的原因,有些线程可能早早地完成了分配给他的任务,也就是他的队列已经空了,但其他的线程还是很忙。这个时候队列已经空了的线程并不会闲置下来,而是随机选择一个其他的线程从队列的尾巴上 “偷走” 一个任务。这个过程会一直继续下去,知道所有的任务都执行完毕,所有的队列都清空。这就是为什么要划成许多小任务而不是少数几个大任务的原因,他能有助于工作线程之间的平衡负载。

工作窃取算法的过程

当工作线程队列中有一个任务被分成两个子任务时,一个子任务就被闲置的工作线程给 “偷走” 了,这个过程可以不断的递归,直到规定子任务应顺序执行的条件为真。下图展示了工作窃取算法用于在池中的工作线程之间重新分配和平衡任务的过程。

总结

相比一般的线程池实现,ForkJoinPool 分支 / 合并框架在对其中包含的任务的处理方式上具有优势,在一般的线程池中,如果一个线程正在执行的任务由于某些原因无法继续运行,那么该线程会处于等待状态。而在 ForkJoinPool 分支 / 合并框架上,如果某个子问题由于等待另外一个子问题的完成而无法继续运行,那么处理该子问题的线程会主动寻找其他尚未运行的子问题(窃取过来)来执行,这种方式而减少了线程池的等待时间,提高了性能。

 

ps:写个博客记录一下自己学习的东西,如有错误请指出,万分感谢。

原文出处:https://www.cnblogs.com/skm-fightting/p/11224618.html

我们今天的关于大家 java 面试中会问 ForkjoinPool 的知识点吗java面试会问到的问题的分享已经告一段落,感谢您的关注,如果您想了解更多关于13、JUC--ForkJoinPool 分支/合并框架 工作窃取、Error:java: Internal compiler error: java.lang.Exception: java.lang.NoClassDefFoundError 解决、ForkJoinPool、ForkJoinPool 分支 / 合并框架工程使用的工作窃取的相关信息,请在本站查询。

本文标签: