与阿里P8大佬面谈半小时。只听到P8老大不紧不慢的问:说说对JDK并发工具的理解?开始认真梳理多年的并发八字随笔经验,道:线程池、Future、CompletableFuture和CompletionService都是并发工具,从任务的角度帮助SE解决并发问题,而不是纠结于相互之间协作的细节线程,比如线程之间如何实现等待和通知。简单的并行任务线程池+FutureCombination任务之间存在聚合关系AND和OR聚合,CompletableFuture是一种新的并行任务批处理方式CompletionService穿梭并发编程可以分为分工、协作、互斥三个层次.当你专注于任务时,你会发现你的视角跳出了并发编程的细节,使用现实世界的思维方式,类比现实世界中的分工。其实线程池、Future、CompletableFuture、CompletionService都可以归为分工问题。简单并行任务、聚合任务、批处理并行任务现实工作流图这三种任务模型基本涵盖了日常工作中的并发场景,但实际上存在“分而治之”的任务模型。divideandconquer,分而治之,解决复杂问题的一种思维方法和模式。将一个复杂的问题分解成多个相似的子问题,再将子问题分解成更小的子问题,直到子问题简单到可以直接求解为止。理论上,解决每一个问题对应一个任务,所以问题的划分实际上就是任务的划分。P8老大直接问了,那么什么是分治任务模型呢?分而治之的任务模型可以分为两个阶段:任务分解迭代地将一个任务分解为子任务,直到可以计算出子任务的结果。该地区的具体事务分配给各个地方管理员。结果合并将子任务的执行结果逐层合并,直到得到最终结果。地方管理者最终向上级汇报治理结果。就像一个官僚系统:你在平时的开发中是怎么使用Fork/Join的?我呢,平时真的没及格,就这么背了。幸运的是,我在面试前准备了这个问题……Fork/Join是一个支持分而治之任务模型的并行计算框架。Fork对应于分治任务模型中的任务分解。Join对应于结果合并。部分:分治任务的线程池ForkJoinPoolForkJoinTask和ForkJoinTask的关系类似于ThreadPoolExecutor和Runnable,都是向线程池提交任务,只是分治任务有自己独特的任务类型ForkJoinTask。ForkJoinTask是JDK7提供的一个抽象类。核心方法如下:fork()异步执行子任务join()阻塞当前线程等待子任务治理任务的执行结果。两个子类都定义了抽象方法compute():RecursiveAction#compute()没有返回值RecursiveTask#compute()有返回值。注意这两个类是抽象类,需要通过子类来定义。只见P8开始冷笑,看来得问源码级的原理了!然后说一下Fork/Join的工作原理。还好我知道阿里的面试套路。对于所有的java工具,你必须深入询问源代码。因为Fork/Join的核心是ForkJoinPool,下面我就深入讲解一下ForkJoinPool的原理。ThreadPoolExecutor本质上是一个生产者-消费者的实现,内部有一个任务队列作为生产者和消费者之间的通信媒介。ThreadPoolExecutor可以有多个工作线程,所有工作线程共享一个任务队列。ForkJoinPool本质上是生产者-消费者的实现,但是更智能的ForkJoinPool工作原理图ThreadPoolExecutor内部只有一个任务队列,而ForkJoinPool内部有多个任务队列,当调用ForkJoinPool#invoke()或submit()提交任务时,ForkJoinPool提交通过路由规则将任务发送到任务队列。如果任务在执行过程中创建了子任务,则子任务会被提交到工作线程对应的任务队列中。如果工作线程对应的任务队列为空,是不是没有工作可做?不!ForkJoinPool有一个“任务窃取”机制。如果工作线程空闲,就会“偷走”其他任务队列中的任务,比如刚才那个图中,如果线程T2对应的任务队列为空,就会“偷走”对应任务队列中的任务到线程T1。这样,所有工作线程都不会空闲。ForkJoinPool的任务队列使用双端队列。工作线程一般从任务队列的不同端获取任务和“窃取任务”消费,这样也可以避免很多不必要的数据竞争。ForkJoinPool支持task-stealing机制,可以让所有线程的工作量基本公平,不会出现忙的线程和有的一直在钓鱼的线程,所以性能非常好,是一个非常公平的leader。Java8的StreamAPI中的并行流也是基于ForkJoinPool的。默认情况下,所有并行流计算共享一个ForkJoinPool,这个共享的ForkJoinPool默认线程数为CPU核数;如果所有的并行流计算都是CPU密集型的,完全没有问题,但是如果是I/O密集型的并行流计算,很可能会因为一个非常慢的I/O而拖慢整个系统的性能计算。因此,建议使用不同的ForkJoinPools来执行不同类型的计算任务。参考https://www.liaoxuefeng.com/article/1146802219354112本文转载自微信公众号「JavaEdge」,可通过以下二维码关注。转载本文请联系JavaEdge公众号。
