Fork-join思路就是将大任务拆分成小任务,在分别计算出结果,最后进行汇总
在简单的分治编程不适合的情况下,推出了ForkJoinPool线程池
在普通的线程池中,如果想要采用分治的思想
那么在将大任务分成小任务的时候,就会将大任务的线程进行阻塞,导致了任务无法正常运行下去
而且线程池自带的大小会导致一件事,就是当阻塞的线程达到了总线程数,那么直接无法执行了
而ForkJoinPool线程则是
加入了工作窃取的算法,即work-stealing算法
工作窃取算法简单来说,就是某个线程从其他线程窃取任务来执行
使用工作窃取算法有什么优势呢?假如我们需要做一个比较大的任务,我们可以把这个任务分割为若干互不依赖的子任务,为了减少线程间的竞争,于是把这些子任务分别放到不同的队列里,并为每个队列创建一个单独的线程来执行队列里的任务,线程和队列一一对应,比如A线程负责处理A队列里的任务。但是有的线程会先把自己队列里的任务干完,而其他线程对应的队列里还有任务等待处理。干完活的线程与其等着,不如去帮其他线程干活,于是它就去其他线程的队列里窃取一个任务来执行。而在这时它们会访问同一个队列,所以为了减少窃取任务线程和被窃取任务线程之间的竞争,通常会使用双端队列,被窃取任务线程永远从双端队列的头部拿任务执行,而窃取任务的线程永远从双端队列的尾部拿任务执行。 |
在线程池中,整个任务分为了外部任务和内部任务
外部任务就是全局队列中的大任务
内部任务则是拆分完成后放在内部队列中的数据
线程切割完成的任务就放在其中,去用于存放
这样的执行流程就是,拿到子任务的计算结果,先判断是否完成,在判断是否被其他队列窃取了,如果都没有就执行
不然就去执行下一个任务
最后这个队列中都执行完成了,就去扫描其他队列的任务,尝试窃取
工作窃取的优点
线程不会因为空了而被阻塞,会高效的进行利用,直到所有的任务都为空,才会阻塞挂起
提供高效的并行性能,不会因为互斥而导致性能上不去
接下来关于整个框架的介绍
首先是三个核心类
ForkJoinPool,线程池
ForkJoinWorkerThread,执行工作的线程,维持了一个内部队列
ForkJoinTask,任务抽象类,实现了Future接口
通常的使用是继承其两个子类
RecursiveTask: 子任务带返回结果时使用
RecursiveAction: 子任务不带返回结果时使用
模式基本为
if 任务足够小
直接返回结果
else
分割成N个子任务
依次调用每个子任务的fork方法执行子任务
依次调用每个子任务的join方法合并执行结果