CompletableFuture的底层ForkJoinPool

什么是 ForkJoinPool?它和普通线程池(ThreadPoolExecutor)有什么区别?

答案要点

ForkJoinPool 是 Java 7 引入的线程池,专为 分治任务 设计,支持递归任务拆分(Fork)和结果合并(Join)。

核心区别:

工作窃取算法(Work-Stealing):空闲线程从其他线程的任务队列尾部窃取任务,提高 CPU 利用率

任务拆分与合并:天然支持递归任务的并行化,而普通线程池需要手动拆分任务

适用场景:ForkJoinPool 适合 CPU 密集型任务(如并行计算),普通线程池适合通用异步任务


ForkJoinPool 的“工作窃取算法”是如何工作的?为什么它能提高效率?

答案要点

每个线程维护一个双端队列(Deque),存放自己的任务

当线程的任务队列为空时,会从其他线程的队列 尾部 窃取任务

高效原因

减少线程空闲时间,最大化 CPU 利用率。

任务拆分和窃取是动态的,适合处理不均衡的任务负载


为什么 CompletableFuture 默认使用 ForkJoinPool?有什么优缺点?

答案要点

优点

工作窃取算法适合处理大量短小的异步任务

公共线程池复用,避免频繁创建/销毁线程的开销

缺点

公共线程池是全局共享的,如果任务阻塞(如 I/O),可能影响其他依赖公共池的任务

默认并行级别可能不适合所有场景(需根据 CPU 核心数调整)

改进方案:为 CompletableFuture 指定自定义线程池:

ExecutorService customPool = Executors.newFixedThreadPool(10);
CompletableFuture.runAsync(() -> {...}, customPool);


如果 ForkJoinPool 的公共线程池被阻塞,会对系统产生什么影响?如何避免?

答案要点

影响

所有依赖公共线程池的功能(如并行流、CompletableFuture)性能下降

可能导致任务排队积压,甚至线程饥饿

避免方法

禁止在公共线程池中执行阻塞操作(如同步 I/O)

为阻塞任务分配独立的线程池


核心设计

1. ForkJoinPool的任务会被内部存储了一个WorkQueue数组,提交给ForkJoinPool的任务会被分配到指定的WorkQueue上执行

2. 每个WorkQueue内部维护了一个ForkJoinTask数组用来存储待执行的任务,以及一个独立的ForkJoinWorkerThread用来真正执行任务

CompletableFuture的底层ForkJoinPool_第1张图片


ForkJoinPool的执行过程

提交任务

以submit方法为例,我们看下ForkJoinPool是如何处理提交的任务的,真正处理的方法是externalPush,如下所示,

1. 判断工作队列数组是否为空

2. 通过按位与得到当前任务分配的工作队列

3. 尝试通过CAS锁定该队列

4. 判断工作队列中的任务数组是否为空

5. 释放锁

执行任务

ForkJoinPool执行任务的过程如下

runWork()

1. 判断工作队列是否需要扩容

2. 阻塞调用对应WorkQueue的runTask方法

3. 如果无法获取到执行资源,就等待任务调用

WorkQueue的rukTask()

1. 标记当前状态为忙碌

2. 调用当前任务执行

3. 继续执行当前队列中等待执行的任务

4. 尝试从其他WorkQueue中窃取任务执行

总结

ForkJoinPool通过内部维护WorkQueue数组的方式,将ForkJoinTask任务分配到指定的工作队列中执行,通过将鸡蛋分散在多个篮子这种分治思想提升线程的工作效率,并且针对线程执行过程中出现的空闲情况,设计出了工作窃取的算法,促使工作线程尽可能的饱和执行,在线程安全方面,通过Unsafe的CAS操作配合原子类,保证了多线程场景下的竞争安全问题


既然forkjoinpool这么高效,那为什么我们实际使用场景中还是要指定自定义线程池

1.公共线程池阻塞问题

默认的ForkJoinPool.commonPool()是全局共享的,如果任务中包含阻塞操作(如I/O、同步锁、数据库查询等),可能导致公共线程池中的线程被长时间占用。这会降低系统的吞吐量,甚至引发线程饥饿(所有线程被阻塞,后续任务无法执行)

若所有类似任务都使用默认线程池,大量阻塞操作会迅速耗尽公共线程池的线程


2.任务选型

CPU密集型任务和IO密集型任务

IO密集型任务不适合ForkJoinPool使用,因为它会长时间占用线程

ForkJoinPool在任务分治和CPU密集型场景中表现出色


3.控制线程池参数

ForkJoinPool.commonPool()的默认配置可能不适用于所有场景

你可能感兴趣的:(java,数据库,网络,spring,线程池,springboot,操作系统)