Java 并发系列七 : JDK中的Fork/Join-单机版的MapReduce

前言

感谢王宝令老师极客时间的 课程,通俗易懂,这里再次推荐

哎,这篇文章敲了一遍没看懂……

背景

前几篇文章我们介绍了线程池,Future 、CompletableFuture 和CompletionService (其中后两者待补充)。仔细观察你会发现这些工具类都是在帮我们站在任务的视角来解决并发问题,而不是让我们纠缠在线程之间的如何协作细节上(比如线程之间如何等待、通知等),对于简单的并行任务,可以通过线程池+Future 的方案来解决,如果任务之间有聚合关系,无论是AND聚合还是OR 聚合,都可以通过CompletableFuture 来解决,而批量的并行任务,则可以通过CompletionService 来解决。

我们一直讲,并发编程可以分为三个层面的问题,分别是分工、协作、和互斥,当你关注于任务的时候,你会发现你的视角已经从并发编程的细节中跳出来了,你应用的更多的是现实世界的思维模式,类比的往往是现实世界里的分工,所以我把线程池、Future 、CompletableFuture 和CompletionService 都列到了分工里面。

下面我们用现实世界里的工作流程图描述了并发编程领域里的简单并行任务、聚合任务和批量执行任务,辅以流程图,相信你一定能将你的思维模式转换到显示世界里来。

img

从上到下,依次为简单并行任务、聚合任务和批量并行任务示意图

上面提到的简单并行、聚合、批量执行这三种任务模型,基本上能够覆盖日常工作中的并发场景了,但是还是不够全面,因为还有一种“分治”的任务模型没有覆盖到。“分治”,顾名思义,分而治之,是一种解决复杂问题的思维方法和模式,具体来讲,指的是把一个复杂的问题分解成多个相似的子问题,然后再把子问题分解成更小的子问题,直到子问题简单到可以直接求解。理论上来讲,解决每一个问题都对应着一个任务,所以对于问题的分治,实际上就是对于任务的分治。

分治思想在很多领域都有广泛应用,例如算法领域有分治算法,(归并排序,快速排序都属于分治算法,二分法查找也属于一种分治算法);大数据领域的知名框架,MapReduce 背后的思想也是分治。既然分治这种任务模型如此普遍,那Java 显然也需要支持,Java 并发包里提供了一种叫做Fork/Join 的 并行框架,就是用来支持分治这种任务模型的。

分治任务模型

这里你需要深入了解下分治任务模型,分治任务模型可以分为两个阶段 一个阶段是任务分解,也就是将任务迭代的分解为子任务,直至子任务可以直接计算出结果。另一个阶段是结果合并,即逐层合并子任务的执行结果,直至获取最终结果。下图是一个简化版的分治任务模型:

img

在这个分治任务模型里,任务和分解后的子任务具有相似性,这种相似性往往体现在任务和子任务的算法是相同的,但是计算的数据规模是不同的,具备这种相似性的问题,我们往往都采用递归的算法。

Fork/Join 的使用

Fork/Join 是一个并行计算框架,主要用来支持分治模型的,这个计算框架里的Fork 对应的是分治任务模型里的分解,Join 对应的结果合并。Fork/Join 计算框架主要包括两部分,一部分是分治任务的线程池ForkJoinPool ,另一部分是分治任务ForkJoinTask 。这两部分的关系类似于ThreadPoolExecutor 和Runnable 的关系,都可以理解为提交任务到线程池,只不过分治任务有自己独特的类型ForkJoinTask 。

ForkJoinTask 是一个抽象类,他的方法很多,最核心的是fork() 方法和 join() 方法 ,其中fork() 方法会异步的执行一个子任务,而join() 方法则会阻塞当前线程来等待子任务的执行结果。ForkJoinTask 有两个子类RecursiveAction 和RecursiveTask ,通过名字你就应该知道,他们都是递归的方式来处理分治任务的。这两个子类都定义了抽象的方法compute(), 不过区别是RecursiveAction 定义的 compute() 没有返回值,而 RecursiveTask 定义的 compute() 方法是有返回值的。 这两个子类也是抽象类,在使用的时候需要你定义子类去扩展。

ForkJoinPool 工作原理

Fork/Join 并行计算的核心组件是ForkJoinPool ,所以下面我们来简单介绍一下ForkJoinPool 的工作原理。

通过前面文章的学习,你应该已经知道了ThreadPoolExecutor 本质上是一个生产者-消费者模式的实现,内部有一个任务队列,这个队列是消费者生产者通信的媒介,ThreadPoolExecutor 可以有多个工作线程,但是这些工作线程都共享一个工作队列。

ForkJoinPool 本质上也是一个消费者-生产者模型的实现,但是更加智能,你可以参考下面的ForkJoinPool 工作原理来理解,ForkJoinPool 内部只有一个任务队列,而ForkJoinPool 内部有多个任务队列,当我们通过ForkJoinPool 的invoke() 或者submit() 方法提交任务的时候,ForkJoinPool 根据一定的路由规则把任务提交到一个任务队列中,如果任务再执行过程中会创建出子任务,那么子任务会提交到工作线程对应的任务队列中。

如果工作线程对应的任务队列空了,是不是就没活干了呢?不是的,ForkJoinPool 支持一种叫做“任务窃取”的机制, 如果工作线程空闲了,那么他可以窃取” 其他工作任务队列中的任务,例如下图中,线程 T2 对应的任务队列已经空了,他可以窃取”线程 T1 对 应的任务队列中的任务,如此一来所有的工作线程都闲不下来了。

ForkJoinPool 中的任务队列采用的是双端队列,工作线程正常获取任务和窃取任务”分别是从任务队列不同的端消费, 这样能避免不必要的数据竞争,我们这里仅仅是简化后的原理,ForkJoinPool 的实现远比我们这里介绍的复杂,如果你感兴趣,建议去看它的源码。

img

模拟 MapReduce 统计单词数量

学习MapReduce 有一个入门程序,统计一个文件里面每个单词的数量,下面我们来看看如何用Fork/Join 并行计算框架实现。

这里省略实现……………………

总结

Fork/Join 并行计算框架主要解决的是分治任务。分治的核心思想是“分而治之”:将一个大的任务拆分成小任务去解决,然后再把子任务的结果聚合起来从而得到最终的结果。这个过程处理非常类似于大数据处理中的MapReduce ,所以你可以把Fork/Join 看作单机版的MapReduce 。

Fork/Join 并行计算框架的核心组件是ForkJoinPool 。ForkJoinPool 支持任务窃取机制,能够让所以的线程工作量基本均衡,不会出现有的线程很忙,有的线程很闲,所以性能很好。Java 1.8 提供的 Stream API 里面并行流也是以ForkJoinPool 为基础的,不过需要注意的是,默认情况下所有的并行流计算都共享一个ForkJoinPool ,这个共享的ForkJoinPool 默认的线程数是CPU 的核数,如果所有的并行流计算都是CPU 密集型计算的话,完全没有问题,但是如果存在I/O 密集型的并行流计算,那么很可能会因为一个慢的I/O 计算而拖慢整个系统的性能,所以建议用不同的ForkJoinPool 执行不同类型的计算任务。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 219,701评论 6 508
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,649评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 166,037评论 0 356
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,994评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,018评论 6 395
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,796评论 1 308
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,481评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,370评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,868评论 1 319
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,014评论 3 338
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,153评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,832评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,494评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,039评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,156评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,437评论 3 373
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,131评论 2 356

推荐阅读更多精彩内容