文章目录
前言
ForkJoinPool
作为线程池,ForkJoinTask
为任务,ForkJoinWorkerThread
作为执行任务的线程,三者构成的任务调度机制在 Java 中通常被称为Fork/Join 框架
ForkJoin
框架在 Java 7 的时候就加入到了 Java 并发包 java.util.concurrent
,并且在 Java 8 的 lambda 并行流
中充当着底层框架的角色。其主要实现的功能是采用“分而治之”的算法将一个大型复杂任务 fork()分解
成足够小的任务才使用多线程去并行处理这些小任务,处理完得到各个小任务的执行结果后再进行join()合并
,将其汇集成一个大任务的结果,最终得到最初提交的那个大型复杂任务的执行结果
ForkJoin框架
最适合的是计算密集型的任务,如果存在 I/O,线程间同步,sleep() 等会造成线程长时间阻塞的情况时会影响整体效率,此时可配合使用 ManagedBlocker
。ForkJoinPool
线程池为了提高任务的并行度和吞吐量做了很多复杂的设计实现,目的是充分利用CPU,其中最著名的就是 work stealing
任务窃取机制
ManagedBlocker
相当于明确告诉ForkJoinPool
有个任务要阻塞了,ForkJoinPool
就会启用另一个线程来运行任务,以最大化地利用CPU
1. ForkJoinPool 的组件
1.1 线程池 ForkJoinPool
ForkJoinPool
的继承体系如下,可以看到它和 ThreadPoolExecutor
一样都是继承自AbstractExecutorService
抽象类,所以它和 ThreadPoolExecutor
的使用几乎没什么区别,只是任务对象变成了 ForkJoinTask
1.1.1 ForkJoinPool 线程池的创建
与其他线程池类似,ForkJoinPool
线程池可使用以下方式创建
Executors.newWorkStealingPool()
public static ExecutorService newWorkStealingPool(int parallelism) { return new ForkJoinPool (parallelism, ForkJoinPool.defaultForkJoinWorkerThreadFactory, null, true); }
构造方法中各个参数的含义如下:
parallelism
: 并行度
即配置线程池线程个数,如果没有指定,则默认为Runtime.getRuntime().availableProcessors() - 1
,最大值不能超过MAX_CAP =32767
,可通过以下方式自定义配置System.setProperty("java.util.concurrent.ForkJoinPool.common.parallelism", "8"); // 或者启动参数指定 -Djava.util.concurrent.ForkJoinPool.common.parallelism=8
factory
: 工作线程创建工厂handler
: 未捕获异常的处理器类
多线程中子线程抛出的异常是无法被主线程 catch 的,因此需要使用UncaughtExceptionHandler
对异常进行统一处理asyncMode
: 任务队列出队模式
默认为false
,使用 LIFO(后入先出,类似栈结构)策略处理任务;为true
时处理策略是 FIFO(先进先出),更接近于一个消息队列,不适用于处理递归式的任务
1.1.2 ForkJoinPool 线程池重要属性
// 配合ctl在控制线程数量时使用
private static final long ADD_WORKER = 0x0001L << (TC_SHIFT + 15);
// 控制ForkJoinPool创建线程数量,(ctl & ADD_WORKER) != 0L 时创建线程,也就是当ctl的第16位不为0时,可以继续创建线程
volatile long ctl; // main pool control
// 全局锁控制,全局运行状态
volatile int runState; // lockable status
// 低16位表示记录并行数量,高16位表示 ForkJoinPool 处理任务的模式(异步/同步)
final int config; // parallelism, mode
// 工作任务队列数组
volatile WorkQueue[] workQueues; // main registry
// 默认线程工厂,默认实现是DefaultForkJoinWorkerThreadFactory
final ForkJoinWorkerThreadFactory factory;
1.2 工作线程 ForkJoinWorkerThread
1.3 线程任务 ForkJoinTask
2. 任务执行流程
3. work stealing 算法
(1)每个工作线程都有自己的工作队列WorkQueue;
(2)这是一个双端队列,它是线程私有的;
(3)ForkJoinTask中fork的子任务,将放入运行该任务的工作线程的队头,工作线程将以LIFO的顺序来处理工作队列中的任务;
(4)为了最大化地利用CPU,空闲的线程将从其它线程的队列中“窃取”任务来执行;
(5)从工作队列的尾部窃取任务FIFO,以减少竞争;
(6)双端队列的操作:push()/pop()仅在其所有者工作线程中调用,poll()是由其它线程窃取任务时调用的;
(7)当只剩下最后一个任务时,还是会存在竞争,是通过CAS来实现的;