ForkJoinPool大型图文现场(一阅到底 vs 直接收藏)

并发工具类我们已经讲了很多,这些工具类的「目标」是让我们只关注任务本身,并且忽视线程间合作细节,简化了并发编程难度的同时,也增加了很多安全性。工具类的对使用者的「目标」虽然一致,但每一个工具类本身都有它独特的应用场景,比如:

我会手动创建线程,为什么要使用线程池? 介绍了使用线程池管理线程将一个大任务分解成多个子任务来简单执行,借助 不会用Java Future,我怀疑你泡茶没我快, 又是超长图文!! 的 Future 特性获取子任务执行结果——二者结合使用就可以处理简单的并行任务

搞定 CompletableFuture,并发异步编程和编写串行程序还有什么区别? 借助 CompletableFuture 大大降低了异步编程的难度——使用串行的思维对任务进行编排执行(AND 或 OR 聚合)

既生 ExecutorService, 何生 CompletionService? 由于任务完成时间有先后,为避免等待阻塞——CompletionService 是批量并行任务的最佳选择

将上面三种通用场景形象化展示一下:

ForkJoinPool大型图文现场(一阅到底 vs 直接收藏)

结合上图相信你的脑海里已经浮现出这几个工具类的具体实现方式,感觉这已经涵盖了所有的并发场景。

ForkJoinPool大型图文现场(一阅到底 vs 直接收藏)

TYTS,以上这些方式的子线程接到任务后不会再继续拆分成「子子」任务,也就是说,子线程即便接到很大或很复杂的任务也得硬着头皮努力执行完,很显然这个大任务是问题关键

如果能把大任务拆分成更小的子问题,直到子问题简单到可以直接求解就好了,这就是分治的思想

分治思想

在计算机科学中,分治法是一种很重要的算法。字面上的解释是「分而治之」,就是把一个复杂的问题分成两个或更多的相同或相似的子问题,再把子问题分成更小的子问题……直到最后子问题可以简单的直接求解,原问题的解就变成了子问题解的合并

这个技巧是很多高效算法的基础,如排序算法 (快速排序,归并排序),傅立叶变换 (快速傅立叶变换)……,如果你是搞大数据的,MapReduce 就是分支思想的典型,如果你想更详细的理解分治相关的算法,请参考这篇一文图解分治算法和思想

结合上面的描述,相信你脑海中已经构建出来分治的模型了:

ForkJoinPool大型图文现场(一阅到底 vs 直接收藏)

那所有的大任务都能用分治算法来解决吗?很显然不是的

分治法适用的情况

总体来说,分治法所能解决的问题一般具有以下几个特征:

该问题的规模缩小到一定的程度就可以容易地解决

该问题可以分解为若干个规模较小的相同问题,即该问题具有最优子结构性质。

利用该问题分解出的子问题的解可以合并为该问题的解;

该问题所分解出的各个子问题是相互独立的,即子问题之间不包含公共的子子问题

了解了分治算法的核心思想,我们就来看看 Java 是如何利用分治思想拆分与合并任务的吧

ForkJoin

有子任务,自然要用到多线程。我们很早之前说过,执行子任务的线程不允许单独创建,要用线程池管理。秉承相同设计理念,再结合分治算法, ForkJoin 框架中就出现了 ForkJoinPool 和 ForkJoinTask。正所谓:

天对地,雨对风。大陆对长空。山花对海树,赤曰对苍穹

套用已有知识,简单理解就是这样滴:

ForkJoinPool大型图文现场(一阅到底 vs 直接收藏)

我们之前说过无数次,JDK 不会重复造轮子,这里谈及相似是为了让大家有个简单的直观印象,内里肯定有所差别,我们先大致看一下这两个类:

ForkJoinTask

又是这个男人,Doug Lea,怎么就那么牛(破音)

/** * Abstract base class for tasks that run within a {@link ForkJoinPool}. * A {@code ForkJoinTask} is a thread-like entity that is much * lighter weight than a normal thread. Huge numbers of tasks and * subtasks may be hosted by a small number of actual threads in a * ForkJoinPool, at the price of some usage limitations. * * @since 1.7 * @author Doug Lea */ public abstract class ForkJoinTask<V> implements Future<V>, Serializable

可以看到 ForkJoinTask 实现了 Future 接口(那就是具有 Future 接口的特性),同样如其名,fork() 和 join() 自然是它的两个核心方法

fork() : 异步执行一个子任务(上面说的拆分)

join() : 阻塞当前线程等待子任务的执行结果(上面说的合并)

另外,从上面代码中可以看出,ForkJoinTask 是一个抽象类,在分治模型中,它还有两个抽象子类 RecursiveAction 和 RecursiveTask

ForkJoinPool大型图文现场(一阅到底 vs 直接收藏)

那这两个子抽象类有什么差别呢?如果你打开 IDE,你应该一眼就能看出差别,so easy

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wpgysx.html