小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。
一、ForkJoin框架
ForkJoinPool是基于分治思想的分治线程池,其过程是把大任务进行拆分,直到拆分成无法再拆分的最小单元,并将拆分后的任务分配给多线程执行,最后把执行结果join。还有工作窃取算法,使得任务能及时被空闲线程处理。ForkJoin框架适用于将大型复杂任务进行递归的分解,直到任务小到指定阈值时开始执行,从而递归的返回各个小任务的结果汇集成一个大任务的结果,依次类推最终得出最初提交的那个大型复杂任务的结果。
二、ForkJoinPool主要类
ForkJoin框架主体由三部分组成: ForkJoinTask:提交到ForkJoinPool中的任务。ForkJoinTask的作用就是根据任务的分解实现,将任务进行拆分,以及等待子任务的执行结果合并成父任务的结果。ForkJoinTask内部存在一个整数类型的成员status,该成员高16位记录任务的执行状态,如:如NORMAL、CANCELLED或EXCEPTIONAL,低16位预留用于记录用户自定义的任务标签。
ForkJoinWorkerThread:运行 ForkJoinTask 任务的工作线程。每个ForkJoinWorkerThread都关联其所属的ForkJoinPool及其工作队列WorkQueue。 ForkJoinWorkerThread线程被创建出来后都交由ForkJoinPool线程池管理,并且设置为了守护线程,由这些线程来执行任务。
ForkJoinPool线程池:ForkJoinPool中,每个ForkJoinWorkerThread线程在创建时,就分配了一个任务队列。同时为了实现工作窃取机制,该队列被设计为双向队列,线程执行自身队列中的任务时,采用LIFO的方式获取任务,当其他线程窃取任务时,采用FIFO的方式获取任务。
ForkJoinPool提交任务的方式也有三种,分别为: execute():对应Runnbale类型的任务; submit():对应Callable类型的任务; invoke():对应ForkJoinTask类型的任务。
三、ForkJoin原理
ForkJoin框架原理
ForkJoin框架是大任务拆中任务,中任务拆小任务,最后再汇总,执行流程大体如下:
提交的任务会被分割成一个个小任务,当分割到最小时,会分别执行每个小的任务,执行完成后,会将每个小任务的结果进行,从而合并出父级任务的结果,依次类推,直至最终计算出整个任务的最终结果。
工作窃取(work-stealing)
ForkJoinPool内部使用的是“工作窃取”算法实现的。即当一个线程的任务队列中没有可执行任务时,会从其他线程的任务队列中窃取任务来执行。在ForkJoinpool中,工作任务的队列都采用双端队列Deque容器。我们知道,在通常使用队列的过程中,我们都在队尾插入,而在队头消费以实现FIFO。而为了实现工作窃取。一般我们会改成工作线程在工作队列上LIFO,而窃取其他线程的任务的时候,从队列头部取获取。示意图如下:
每个工作线程都有自己的工作队列WorkQueue,ForkJoinTask中fork的子任务,将放入运行该任务的工作线程的队头,工作线程将以LIFO的顺序来处理工作队列中的任务。空闲的线程将从其它线程的队列中“窃取”任务来执行,从工作队列的尾部窃取任务,以减少竞争。双端队列的操作:push()/pop()仅在其所有者工作线程中调用,poll()是由其它线程窃取任务时调用的。实际当只剩下最后一个任务时还是会存在竞争(这跟CAS有关)。