前言
ForkJoinPool 是为 “计算密集、可分治” 的任务设计的,其工作窃取和并行度优化能最大化 CPU 利用率(PS:IO 密集型任务因大量阻塞操作,无法利用 ForkJoinPool 的优势)
核心概念
特点
- 任务分治
- 分治算法思想,只有可分治的任务才适合使用
- 任务窃取
- 双端队列数据结构,线程调度,任务调度
工作流
- 提交任务 → 外部提交或内部fork
- 工作线程执行 → 从自己队列取任务
- 空闲线程窃取 → 从其他队列偷任务
- 任务完成 → 结果合并或继续分解
- 资源回收 → 线程复用或销毁
三大核心组件
- 任务系统
- RecursiveTask: 有返回值的任务
- RecursiveAction: 无返回值的任务
- fork()/join(): 任务分解和合并
- 线程系统
- 工作线程: 执行任务
- 工作队列: 每个线程一个双端队列
- 调度系统
- 工作窃取算法
- 线程管理
- 任务调度
应用
为什么需要?解决了什么问题?怎么使用?
当出现大计算量且能分割的任务时,常规线程池无法满足需求(适用多任务或多无关联子任务,线程各自执行,且存在线程饥饿),而ForkJoinPool可以通过任务调度和线程调度实现最大化CPU效率利用,使各线程协作完成。
大量密集计算
最适合计算密集型的可分治任务
demo
/**
* 企业场景:科学计算、机器学习中的大规模矩阵乘法
* 这是真正的计算密集型任务,能明显体现ForkJoinPool优势
*/
public class MatrixMultiplicationBenchmark {
static class MatrixMultiplyTask extends RecursiveTask<double[][]> {
private final double[][] A, B, C;
private final int rowStart, rowEnd, colStart, colEnd;
private final int size;
private static final int THRESHOLD = 64; // 基于CPU缓存行优化
public MatrixMultiplyTask(double[][] A, double[][] B, double[][] C,
int rowStart, int rowEnd, int colStart, int colEnd) {
this.A = A; this.B = B; this.C = C;
this.rowStart = rowStart; this.rowEnd = rowEnd;
this.colStart = colStart; this.colEnd = colEnd;
this.size = A.length;
}
@Override
protected double[][] compute() {
int rows = rowEnd - rowStart;
int cols = colEnd - colStart;
// 如果矩阵块足够小,直接计算
if (rows <= THRESHOLD && cols <= THRESHOLD) {
for (int i = rowStart; i < rowEnd; i++) {
for (int j = colStart; j < colEnd; j++) {
double sum = 0;
for (int k = 0; k < size; k++) {
sum += A[i][k] * B[k][j];
}
C[i][j] = sum;
}
}
return C;
}
// 递归分解为4个子任务
if (rows > cols) {
// 水平拆分
int midRow = rowStart + rows / 2;
MatrixMultiplyTask top = new MatrixMultiplyTask(A, B, C, rowStart, midRow, colStart, colEnd);
MatrixMultiplyTask bottom = new MatrixMultiplyTask(A, B, C, midRow, rowEnd, colStart, colEnd);
top.fork();
bottom.compute();
top.join();
} else {
// 垂直拆分
int midCol = colStart + cols / 2;
MatrixMultiplyTask left = new MatrixMultiplyTask(A, B, C, rowStart, rowEnd, colStart, midCol);
MatrixMultiplyTask right = new MatrixMultiplyTask(A, B, C, rowStart, rowEnd, midCol, colEnd);
left.fork();
right.compute();
left.join();
}
return C;
}
}
// 单线程版本用于对比
public static double[][] singleThreadMultiply(double[][] A, double[][] B) {
int n = A.length;
double[][] C = new double[n][n];
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
double sum = 0;
for (int k = 0; k < n; k++) {
sum += A[i][k] * B[k][j];
}
C[i][j] = sum;
}
}
return C;
}
public static void main(String[] args) {
int size = 1024; // 1024x1024矩阵,计算量足够大
System.out.println("生成 " + size + "x" + size + " 矩阵...");
double[][] A = generateRandomMatrix(size);
double[][] B = generateRandomMatrix(size);
double[][] C = new double[size][size];
// 单线程测试
System.out.println("\n=== 单线程矩阵乘法 ===");
long startTime = System.currentTimeMillis();
double[][] result1 = singleThreadMultiply(A, B);
long endTime = System.currentTimeMillis();
System.out.println("单线程耗时: " + (endTime - startTime) + "ms");
// ForkJoinPool测试
System.out.println("\n=== ForkJoinPool矩阵乘法 ===");
ForkJoinPool pool = new ForkJoinPool();
startTime = System.currentTimeMillis();
MatrixMultiplyTask task = new MatrixMultiplyTask(A, B, C, 0, size, 0, size);
double[][] result2 = pool.invoke(task);
endTime = System.currentTimeMillis();
System.out.println("ForkJoinPool耗时: " + (endTime - startTime) + "ms");
// 验证结果一致性
System.out.println("\n=== 结果验证 ===");
boolean correct = verifyResults(result1, result2, 0.0001);
System.out.println("结果一致性: " + (correct ? "✓ 正确" : "✗ 错误"));
pool.shutdown();
}
private static double[][] generateRandomMatrix(int size) {
Random rand = new Random();
double[][] matrix = new double[size][size];
for (int i = 0; i < size; i++) {
for (int j = 0; j < size; j++) {
matrix[i][j] = rand.nextDouble();
}
}
return matrix;
}
private static boolean verifyResults(double[][] A, double[][] B, double tolerance) {
for (int i = 0; i < A.length; i++) {
for (int j = 0; j < A[i].length; j++) {
if (Math.abs(A[i][j] - B[i][j]) > tolerance) {
return false;
}
}
}
return true;
}
}
三层循环是矩阵乘法数学定义的直接体现:
- 第一层:遍历结果矩阵的行
- 第二层:遍历结果矩阵的列
- 第三层:计算行与列的点积(内积)
这种O(n³)的时间复杂度使得矩阵乘法成为展示并行计算优势的完美案例,因为计算量足够大,多核并行能带来明显的性能提升。
结果对比
生成 1024x1024 矩阵...
=== 单线程矩阵乘法 ===
单线程耗时: 1645ms
=== ForkJoinPool矩阵乘法 ===
ForkJoinPool耗时: 352ms
=== 结果验证 ===
结果一致性: ✓ 正确
并行流
java8-parallelStream并行流底层正是ForkJoinPool的common池应用
进行简单大规模数据过滤和转换处理
demo
public class ParallelVsSequentialDemo {
// 判断是否为质数(计算密集型)
private static boolean isPrime(long n) {
if (n <= 1) return false;
if (n == 2 || n == 3) return true;
if (n % 2 == 0) return false;
// 只需检查到 sqrt(n)
long limit = (long) Math.sqrt(n);
for (long i = 3; i <= limit; i += 2) {
if (n % i == 0) return false;
}
return true;
}
public static void main(String[] args) {
// 测试范围:计算从1到1000万的质数个数
final long RANGE_START = 1;
final long RANGE_END = 10_000_000L;
System.out.println("测试范围: " + RANGE_START + " 到 " + RANGE_END);
System.out.println("处理器核心数: " + Runtime.getRuntime().availableProcessors());
System.out.println("ForkJoinPool公共池并行度: " + ForkJoinPool.getCommonPoolParallelism());
// 测试1:串行流
System.out.println("\n--- 测试1:串行流计算 ---");
long serialStartTime = System.currentTimeMillis();
long serialCount = LongStream.rangeClosed(RANGE_START, RANGE_END)
.filter(ParallelVsSequentialDemo::isPrime)
.count();
long serialEndTime = System.currentTimeMillis();
System.out.println("串行流耗时: " + (serialEndTime - serialStartTime) + " ms");
System.out.println("质数个数: " + serialCount);
// 测试2:并行流(使用默认ForkJoinPool公共池)
System.out.println("\n--- 测试2:并行流计算(默认公共池)---");
long parallelStartTime = System.currentTimeMillis();
long parallelCount = LongStream.rangeClosed(RANGE_START, RANGE_END)
.parallel() // 关键:转换为并行流
.filter(ParallelVsSequentialDemo::isPrime)
.count();
long parallelEndTime = System.currentTimeMillis();
System.out.println("并行流耗时: " + (parallelEndTime - parallelStartTime) + " ms");
System.out.println("质数个数: " + parallelCount);
// 性能对比
System.out.println("\n=== 性能对比 ===");
System.out.println("并行流加速比: " +
String.format("%.2f", (double)(serialEndTime - serialStartTime) /
(parallelEndTime - parallelStartTime)) + " 倍");
// 测试3:使用自定义ForkJoinPool(可选)
System.out.println("\n--- 测试3:自定义ForkJoinPool(4线程) ---");
ForkJoinPool customPool = new ForkJoinPool(4);
long customStartTime = System.currentTimeMillis();
long customCount = customPool.submit(() ->
LongStream.rangeClosed(RANGE_START, RANGE_END)
.parallel()
.filter(ParallelVsSequentialDemo::isPrime)
.count()
).join();
long customEndTime = System.currentTimeMillis();
System.out.println("自定义池耗时: " + (customEndTime - customStartTime) + " ms");
System.out.println("质数个数: " + customCount);
customPool.shutdown();
}
}
结果
从结果可以看出自定义ForkJoinPool与parallelStream效率几乎一致并且高出几倍,而像这种简单转换、过滤、映射的业务场景parallelStream的API更加便捷易读。注意:parallelStream底层用的common池是JVM管理的全局单例,是整个应用的parallelStream共享的,千万别在里面做IO操作会影响全局性能。
测试范围: 1 到 10000000
处理器核心数: 8
ForkJoinPool公共池并行度: 7
--- 测试1:串行流计算 ---
串行流耗时: 699 ms
质数个数: 664579
--- 测试2:并行流计算(默认公共池)---
并行流耗时: 199 ms
质数个数: 664579
=== 性能对比 ===
并行流加速比: 3.51 倍
--- 测试3:自定义ForkJoinPool(4线程) ---
自定义池耗时: 199 ms
质数个数: 664579
基本API
构造实例
public class ForkJoinPoolAPIDemo {
public void demonstrateConstructors() {
// 1. 默认构造 - 使用可用处理器数作为并行度
ForkJoinPool pool1 = new ForkJoinPool();
// 2. 指定并行度 - 控制并发线程数
ForkJoinPool pool2 = new ForkJoinPool(8);
// 3. 公共池 - JVM管理全局唯一,轻量级任务可用
ForkJoinPool commonPool = ForkJoinPool.commonPool();
// 4. 自定义实例 - 大型计算独享,定制度高
ForkJoinPool customPool = new ForkJoinPool(
Runtime.getRuntime().availableProcessors(),// 并行度
new BusinessThreadFactory("业务计算池"), // 自定义线程工厂
new BusinessExceptionHandler(), // 自定义异常处理器
false // 异步模式
);
}
}
自定义实例 vs 通用实例
| 特性 | new ForkJoinPool() | ForkJoinPool.commonPool() |
|---|---|---|
| 实例类型 | 创建新的独立实例 | JVM 全局共享的单例 |
| 资源隔离 | 完全隔离,任务不会受其他无关任务(如其他库的并行流)干扰,也不会影响它们。 | 全局共享,所有使用并行流的代码(包括第三方库)都在这里竞争资源。 |
| 参数调优 | 可精细控制并行度(parallelism)、线程工厂、异常处理等。 | 配置固定,仅能通过JVM参数 -Djava.util.concurrent.ForkJoinPool.common.parallelism=N调整并行度。 |
| 生命周期 | 自主管理,可随应用组件启动和关闭 | JVM全局,随JVM消亡,无法主动关闭或重启 |
| 使用场景 | 隔离的、需要特殊配置的任务 | 通用的、轻量级的并行任务 |
任务提交
根据业务需要选择不同方式提交任务
任务类型
- RecursiveTask: 需要返回结果的计算密集型任务
- RecursiveAction: 不需要返回结果的操作型任务
public class TaskSubmissionDemo {
public void demonstrateSubmissionMethods() throws Exception {
ForkJoinPool pool = new ForkJoinPool();
RecursiveTask<Integer> task = new SimpleSumTask(1, 100);
// 1. invoke() - 同步执行并等待结果
Integer result1 = pool.invoke(task);
System.out.println("invoke结果: " + result1);
// 2. submit() - 提交任务,返回Future
ForkJoinTask<Integer> future = pool.submit(task);
Integer result2 = future.get();
System.out.println("submit结果: " + result2);
// 3. execute() - 异步执行,不关心结果
pool.execute(task);
// 4. invokeAll() - 批量执行多个任务
List<RecursiveTask<Integer>> tasks = Arrays.asList(
new SimpleSumTask(1, 50),
new SimpleSumTask(51, 100)
);
List<Future<Integer>> futures = pool.invokeAll(tasks);
pool.shutdown();
}
static class SimpleSumTask extends RecursiveTask<Integer> {
private final int start;
private final int end;
public SimpleSumTask(int start, int end) {
this.start = start;
this.end = end;
}
@Override
protected Integer compute() {
if (end - start <= 10) {
int sum = 0;
for (int i = start; i <= end; i++) {
sum += i;
}
return sum;
}
int mid = (start + end) / 2;
SimpleSumTask left = new SimpleSumTask(start, mid);
SimpleSumTask right = new SimpleSumTask(mid + 1, end);
left.fork();
return right.compute() + left.join();
}
}
}
调优与注意
1. 自定义参数
- 核心数
- 线程工厂
- 异常处理
| 配置项 | 推荐值 | 说明 |
|---|---|---|
| 并行度 | CPU核心数-1 或 CPU核心数*0.75 | 留资源给系统线程,避免过度竞争 |
| 线程工厂 | ProductionThreadFactory | 自定义线程名、异常处理器、非守护线程 |
| 异常处理 | 必须设置 UncaughtExceptionHandler | 防止异常静默消失,记录日志 |
| 异步模式 | false (FIFO) | 计算密集型递归任务适合FIFO |
| 任务阈值 | 总数据量/(并行度*12) | 确保每个任务执行5-50ms |
| 关闭超时 | 30秒 | 先温和关闭,超时后强制关闭 |
生产可用完整配置
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.ForkJoinWorkerThread;
import java.util.concurrent.RecursiveTask;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
/**
* 生产环境ForkJoinPool最佳配置
* 适用于密集型可分割计算任务
*/
public class ProductionForkJoinPool {
// ==================== 核心配置参数 ====================
/**
* 最优并行度计算:
* 1. 计算密集型任务:使用物理核心数(非逻辑核心)
* 2. 留1个核心给系统和其他线程
* 3. 最小为1,防止无可用线程
*/
private static final int OPTIMAL_PARALLELISM = Math.max(1,
Runtime.getRuntime().availableProcessors() - 1);
/**
* 任务粒度阈值经验值:
* - 每个子任务执行时间建议在 1-10ms
* - 根据任务复杂度调整此值
*/
private static final int DEFAULT_TASK_THRESHOLD = 10000;
// ==================== 自定义线程工厂(带监控) ====================
static class ProductionThreadFactory implements ForkJoinPool.ForkJoinWorkerThreadFactory {
private final AtomicInteger threadCounter = new AtomicInteger(0);
private final String poolName;
public ProductionThreadFactory(String poolName) {
this.poolName = poolName;
}
@Override
public ForkJoinWorkerThread newThread(ForkJoinPool pool) {
ForkJoinWorkerThread thread = new ProductionWorkerThread(pool);
thread.setName(String.format("%s-worker-%d",
poolName, threadCounter.incrementAndGet()));
thread.setPriority(Thread.NORM_PRIORITY);
thread.setDaemon(false); // 非守护线程,防止任务意外终止
// 设置未捕获异常处理器(必须设置!)
thread.setUncaughtExceptionHandler((t, e) -> {
System.err.printf("[ERROR] 工作线程 %s 发生未捕获异常: %s%n",
t.getName(), e.toString());
e.printStackTrace();
// 这里可以集成到你的日志框架
// logger.error("ForkJoinWorkerThread uncaught exception", e);
});
return thread;
}
}
// ==================== 自定义工作线程(添加监控指标) ====================
static class ProductionWorkerThread extends ForkJoinWorkerThread {
private final AtomicLong tasksCompleted = new AtomicLong(0);
private final AtomicLong totalComputeTime = new AtomicLong(0);
protected ProductionWorkerThread(ForkJoinPool pool) {
super(pool);
}
public void recordTaskCompletion(long executionTimeMs) {
tasksCompleted.incrementAndGet();
totalComputeTime.addAndGet(executionTimeMs);
}
public long getTasksCompleted() {
return tasksCompleted.get();
}
public double getAverageTaskTimeMs() {
long completed = tasksCompleted.get();
return completed == 0 ? 0 : (double) totalComputeTime.get() / completed;
}
}
// ==================== 异常安全的任务包装器 ====================
/**
* 包装任务,确保异常被正确捕获和记录
*/
public static <T> T executeSafely(RecursiveTask<T> task, ForkJoinPool pool) {
try {
return pool.invoke(task);
} catch (Exception e) {
// 记录任务执行异常
System.err.printf("[ERROR] 任务执行失败: %s%n", e.toString());
e.printStackTrace();
// 如果是计算错误,可以返回默认值或重新抛出
// 根据业务需求决定:return defaultValue; 或 throw e;
throw new RuntimeException("计算任务执行失败", e);
}
}
// ==================== 创建最佳配置的线程池 ====================
/**
* 创建生产环境最优配置的ForkJoinPool
*
* @param poolName 线程池名称(用于监控和日志)
* @param parallelism 并行度,null则使用自动计算的优化值
* @return 配置好的ForkJoinPool实例
*/
public static ForkJoinPool createOptimalPool(String poolName, Integer parallelism) {
int actualParallelism = (parallelism != null && parallelism > 0)
? parallelism : OPTIMAL_PARALLELISM;
System.out.printf("创建ForkJoinPool [%s], 并行度: %d (CPU核心数: %d)%n",
poolName, actualParallelism, Runtime.getRuntime().availableProcessors());
return new ForkJoinPool(
actualParallelism, // 并行度
new ProductionThreadFactory(poolName), // 自定义线程工厂
new Thread.UncaughtExceptionHandler() {
// 线程死亡处理器(线程异常终止时的处理)
@Override
public void uncaughtException(Thread t, Throwable e) {
System.err.printf("[FATAL] 线程 %s 异常终止: %s%n",
t.getName(), e.toString());
// 这里可以发送告警邮件/短信
}
},
false, // 异步模式:false (FIFO) - 适合有依赖的递归计算
// 以下为内部队列参数,通常使用默认值即可
0, // 核心线程数,ForkJoinPool中通常等于并行度
actualParallelism * 2, // 最大线程数,设置为并行度的2倍作为安全边界
60, // 空闲线程保持时间(秒)
java.util.concurrent.TimeUnit.SECONDS,
null, // 工作队列,null使用默认的LIFO队列
// 拒绝处理器 - ForkJoinPool通常不需要,因为工作队列无限
null
);
}
// ==================== 监控工具类 ====================
/**
* 线程池监控信息
*/
public static class PoolMonitor {
public static void printStats(ForkJoinPool pool, String poolName) {
System.out.printf("=== ForkJoinPool [%s] 状态监控 ===%n", poolName);
System.out.printf("并行度: %d%n", pool.getParallelism());
System.out.printf("活动线程: %d%n", pool.getActiveThreadCount());
System.out.printf("池大小: %d%n", pool.getPoolSize());
System.out.printf("运行线程: %d%n", pool.getRunningThreadCount());
System.out.printf("窃取次数: %,d%n", pool.getStealCount());
System.out.printf("排队任务: %,d%n", pool.getQueuedTaskCount());
System.out.printf("队列提交数: %,d%n", pool.getQueuedSubmissionCount());
System.out.printf("是否异步: %s%n", pool.getAsyncMode() ? "LIFO" : "FIFO");
System.out.printf("是否终止: %s%n", pool.isTerminated());
System.out.printf("是否关闭: %s%n", pool.isShutdown());
System.out.println();
}
/**
* 监控并确保线程池健康
*/
public static boolean isPoolHealthy(ForkJoinPool pool) {
return pool != null &&
!pool.isShutdown() &&
!pool.isTerminated() &&
pool.getActiveThreadCount() > 0;
}
}
// ==================== 安全关闭工具 ====================
/**
* 安全关闭线程池
*/
public static void shutdownSafely(ForkJoinPool pool, long timeoutSeconds) {
if (pool == null || pool.isShutdown()) {
return;
}
System.out.println("开始安全关闭线程池...");
// 1. 不再接受新任务
pool.shutdown();
try {
// 2. 等待现有任务完成
if (!pool.awaitTermination(timeoutSeconds, java.util.concurrent.TimeUnit.SECONDS)) {
System.err.println("线程池未在指定时间内关闭,尝试强制关闭...");
// 3. 尝试取消所有任务
pool.shutdownNow();
// 4. 再等待一段时间
if (!pool.awaitTermination(5, java.util.concurrent.TimeUnit.SECONDS)) {
System.err.println("线程池强制关闭失败,可能仍有任务在执行");
}
} else {
System.out.println("线程池已安全关闭");
}
} catch (InterruptedException e) {
// 重新设置中断状态
Thread.currentThread().interrupt();
System.err.println("关闭线程池时被中断");
pool.shutdownNow();
}
}
// ==================== 使用示例 ====================
/**
* 示例:大数据数组求和任务
*/
static class BigArraySumTask extends RecursiveTask<Long> {
private final long[] array;
private final int start;
private final int end;
private final int threshold; // 可配置的阈值
public BigArraySumTask(long[] array, int start, int end, int threshold) {
this.array = array;
this.start = start;
this.end = end;
this.threshold = threshold;
}
@Override
protected Long compute() {
int length = end - start;
// 基线条件:达到阈值时顺序计算
if (length <= threshold) {
long startTime = System.nanoTime();
long sum = 0;
// 手动循环展开,提高计算性能
int i = start;
int limit = end - 3;
for (; i <= limit; i += 4) {
sum += array[i] + array[i + 1] + array[i + 2] + array[i + 3];
}
for (; i < end; i++) {
sum += array[i];
}
// 记录任务执行时间(生产环境可移除)
long executionTimeMs = (System.nanoTime() - startTime) / 1_000_000;
if (executionTimeMs > 100) { // 记录耗时较长的任务
System.out.printf("叶子任务完成: 处理 %,d 个元素,耗时 %,d ms%n",
length, executionTimeMs);
}
return sum;
}
// 递归分割
int mid = start + (length / 2);
BigArraySumTask left = new BigArraySumTask(array, start, mid, threshold);
BigArraySumTask right = new BigArraySumTask(array, mid, end, threshold);
// 提交左任务到队列
left.fork();
// 计算右任务并合并结果
Long rightResult = right.compute();
Long leftResult = left.join();
return leftResult + rightResult;
}
}
// ==================== 主方法测试 ====================
public static void main(String[] args) {
// 1. 创建最优配置的线程池
ForkJoinPool pool = createOptimalPool("BigData-Compute-Pool", null);
try {
// 2. 生成测试数据(1000万随机数)
int dataSize = 10_000_000;
long[] data = new long[dataSize];
for (int i = 0; i < dataSize; i++) {
data[i] = (long) (Math.random() * 1000);
}
System.out.printf("生成测试数据完成,共 %,d 个元素%n", dataSize);
// 3. 创建计算任务
// 阈值根据数据量和任务复杂度调整:总数据量 / (并行度 * 8~16)
int threshold = Math.max(1000, dataSize / (OPTIMAL_PARALLELISM * 12));
BigArraySumTask task = new BigArraySumTask(data, 0, dataSize, threshold);
// 4. 监控初始状态
PoolMonitor.printStats(pool, "BigData-Compute-Pool");
// 5. 安全执行任务
long startTime = System.currentTimeMillis();
Long result = executeSafely(task, pool);
long endTime = System.currentTimeMillis();
// 6. 监控执行后状态
PoolMonitor.printStats(pool, "BigData-Compute-Pool");
// 7. 输出结果
System.out.printf("计算完成!总和: %,d%n", result);
System.out.printf("总耗时: %,d ms%n", (endTime - startTime));
// 验证结果(顺序计算验证)
long verifySum = 0;
for (long num : data) {
verifySum += num;
}
if (result.equals(verifySum)) {
System.out.println("✓ 结果验证正确");
} else {
System.err.println("✗ 结果验证失败!并行计算与顺序计算结果不一致");
System.err.printf("并行结果: %,d, 顺序结果: %,d%n", result, verifySum);
}
} finally {
// 8. 安全关闭线程池
shutdownSafely(pool, 30);
}
}
}
2. 监控和管理
正常关闭流程:优先使用 shutdown() + awaitTermination(),确保任务完整执行
public class PoolMonitoringDemo {
public void demonstrateMonitoring() {
ForkJoinPool pool = new ForkJoinPool(4);
// 监控相关API
System.out.println("并行度: " + pool.getParallelism());
System.out.println("池大小: " + pool.getPoolSize());
System.out.println("活跃线程数: " + pool.getActiveThreadCount());
System.out.println("运行线程数: " + pool.getRunningThreadCount());
System.out.println("窃取次数: " + pool.getStealCount());
System.out.println("任务队列数: " + pool.getQueuedTaskCount());
System.out.println("提交任务数: " + pool.getQueuedSubmissionCount());
// 管理方法
// 优雅关闭 (拒绝新任务,等待已有任务)
pool.shutdown();
// 超时后关闭
if (!pool.awaitTermination(60, TimeUnit.SECONDS)) {
// 强制关闭
pool.shutdownNow();
}
// awaitQuiescence(); 等待所有任务(含子任务)完成,但不关闭线程池,复用线程池(使用频率不高)
}
}
3. 注意事项
-
设置合适的任务粒度 (
THRESHOLD)**- 目的:每个叶子任务的计算时间应远大于任务拆分和调度的开销。
- 经验值:在
ForkJoinPool中,这个时间最好在 1毫秒到10毫秒 之间。你可以通过性能测试来调整。 - 公式参考:阈值 ≈ 总数据量 / (并行度 * 8 ~ 16)。上述例子中
10_000就是一个起点,你需要根据单次计算的成本调整。
-
绝对避免在任务中阻塞**
- 牢记:
ForkJoinPool的每个工作线程都是宝贵资源。绝对不要在compute()方法中进行任何同步 I/O 操作(如文件读写、网络请求、Thread.sleep)。 - 后果:一旦一个线程被阻塞,它就无法去窃取其他任务,整个池的吞吐量会急剧下降,可能还不如单线程快。
- 如果必须有I/O:将I/O操作与计算分离。使用专门的I/O线程池(如
Executors.newCachedThreadPool)处理阻塞部分,将结果通过CompletableFuture等机制传递给ForkJoinPool进行纯计算。
- 牢记:
原理
从ForkJoinPool的特点出发(任务分割,工作窃取,线程调度),深入源码可知,框架设计由分治算法思想决定,任务的操作主要是对线程各自的双端队列操作(push/pop/poll);线程状态管理由ForkJoinPool的ctl字段统筹;
即需要掌握的有
- 理解分治思想
- 任务分解,并行执行,结果归并
- 熟悉双端队列操作和索引维护
- 掌握ctl-线程状态管理
- 了解线程调度工作流
再熟悉下工作流

任务分治
ForkJoinPool的分治设计哲学体现了递归分解、并行执行、结果合并的核心理念
分治思想的应用
从其命名就可以看出他的特点:Fork 任务拆分 Join 结果合并 Pool 多线程并行,可以将大任务当成一颗二叉树的根节点,由上至下一直切割满足我们条件的叶子节点,最终再由底至上归并结果。这种分治思想的应用还可以联想下以前学的归并排序,同样是 分割-合并结果。
相较于传统线性分解计算(如传统单线程递归任务实现),ForkJoinPool 能充分利用多核CPU资源并行计算使其能满足大规模数据任务下的高效性。
public class BinaryDecompositionExample {
// 传统线性分解 vs ForkJoinPool的二叉树分解
// ❌ 传统线性分解(效率低):
// 任务1 → 任务2 → 任务3 → 任务4
// 串行依赖,无法充分利用并行
// ✅ ForkJoinPool二叉树分解(高效):
// 总任务
// / \
// 左子树 右子树
// / \ / \
// 左左 左右 右左 右右
// 完全并行,充分利用多核
}
直观看下任务分割
public class FJSImpleDemo {
static class SimpleTask extends RecursiveTask<Integer> {
private final int value;
private final String name;
SimpleTask(int value, String name) {
this.value = value;
this.name = name;
}
@Override
protected Integer compute() {
System.out.println(Thread.currentThread().getName() + " 执行任务: " + name);
if (value <= 1) {
return value;
}
// 模拟计算
try {
Thread.sleep(100);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
SimpleTask left = new SimpleTask(value - 1, name + "-左");
SimpleTask right = new SimpleTask(value - 2, name + "-右");
left.fork(); // 异步执行左任务
int rightResult = right.compute(); // 当前线程执行右任务
int leftResult = left.join(); // 等待左任务完成
return leftResult + rightResult;
}
}
public static void main(String[] args) throws InterruptedException {
ForkJoinPool pool = new ForkJoinPool(2);
SimpleTask mainTask = new SimpleTask(4, "主任务");
Integer result = pool.invoke(mainTask);
System.out.println("最终结果: " + result);
pool.awaitTermination(1, TimeUnit.SECONDS);
pool.shutdown();
}
}
线程2 任务4
value<=1 return
左 -1
右 -2
ForkJoinPool-1-worker-1 执行任务: 主任务
ForkJoinPool-1-worker-1 执行任务: 主任务-右 2
ForkJoinPool-1-worker-0 执行任务: 主任务-左 3
ForkJoinPool-1-worker-0 执行任务: 主任务-左-右 1 -end
ForkJoinPool-1-worker-0 执行任务: 主任务-左-左 2
ForkJoinPool-1-worker-1 执行任务: 主任务-右-右 0 -end
ForkJoinPool-1-worker-1 执行任务: 主任务-右-左 1 -end
ForkJoinPool-1-worker-0 执行任务: 主任务-左-左-右 0-end
ForkJoinPool-1-worker-0 执行任务: 主任务-左-左-左 1-end
最终结果: 3
阈值(Threshold)设计哲学
阈值是分治算法的灵魂,决定了任务分解的粒度
public class ThresholdDesign {
// 阈值的核心作用:平衡并行收益与调度开销
private static final int OPTIMAL_THRESHOLD = findOptimalThreshold();
private static int findOptimalThreshold() {
// 理想阈值应该满足:
// 1. 足够小:能够充分利用所有CPU核心
// 2. 足够大:避免任务调度开销超过计算收益
// 3. 自适应:根据任务类型和系统资源动态调整
return Runtime.getRuntime().availableProcessors() * 100;
}
// 阈值设置的黄金法则:
public class ThresholdGoldenRules {
// ✅ 计算密集型任务:较小阈值(100-1000个元素)
// ✅ 内存访问密集型:考虑缓存行大小(通常64字节)
// ❌ 避免极端:不要太小(调度开销)或太大(无法并行)
}
}
递归任务的生命周期
public class RecursiveTaskLifecycle {
// ForkJoinTask的完整生命周期:
public class TaskLifecycle {
// 阶段1: 任务创建与提交
// 阶段2: 递归分解(直到达到阈值)
// 阶段3: 叶子任务执行(直接计算)
// 阶段4: 结果合并(从叶子到根)
// 阶段5: 任务完成与清理
}
// 具体实现模式:
@Override
protected Long compute() {
if (达到阈值) {
return 直接计算(); // 叶子节点
} else {
leftTask.fork(); // 异步执行左子树
rightResult = rightTask.compute(); // 同步执行右子树
leftResult = leftTask.join(); // 等待左子树
return 合并结果(leftResult, rightResult); // 合并
}
}
}
双端队列(Deque)
关键的任务处理:push入队、pop本地出队、poll任务窃取 都是对双端队列进行操作,而双端队列本质为可扩容数组,根据top 和 base指针进行安全协调处理,以下结合ForkJoinPool的源码来看下双端队列的运作
双端队列的结构与索引
数组: array[length] (length=2^n, 如8,16,32)
掩码: mask = length - 1 (二进制低位全1,如7=0111, 15=1111)
索引指针:
- top: 只被所有者线程修改,指向下一个push位置
- base: volatile,被所有线程读取,指向下一个poll位置
逻辑长度 = top - base (即使int溢出也正确)
物理索引 = pointer & mask (环形映射)
1. push 任务 (LIFO 入队)
初始: top=0, base=0, length=8, mask=7
数组: [_, _, _, _, _, _, _, _]
push(T1):
读取: b=base=0, s=top=0
索引: 0 & 7 = 0
存入: array[0] = T1
top = s + 1 = 1
检查: n = s - b = 0 < 7 → 不扩容
结果: top=1, base=0, 数组[T1,_,_,_,_,_,_,_]
2. 临界扩容判断
连续push后: top=7, base=0
数组: [T1,T2,T3,T4,T5,T6,T7,_]
push(T8):
读取: b=base=0, s=top=7
索引: 7 & 7 = 7
存入: array[7] = T8
top = 7 + 1 = 8
检查: n = s - b = 7 >= 7 ✓ → 触发growArray()
这里为什么是比较length-1=7,而不是length,是为了防止top会回绕到0覆盖未消费的任务
3. pop任务-正常
当前: top=5, base=2
数组: [_, _, T3, T4, T5, _, _, _] (索引2,3,4有任务)
pop():
s = top = 5
尝试位置: s-1 = 4
索引: 4 & 7 = 4
读取: array[4] = T5
CAS清空: array[4] = null ✓
更新: top = 4
结果: top=4, base=2, T5被取出
竞争场景:
pop()时与poll()竞争
s = top = 5, s-1=4
索引4: 读取到T5
但CAS前,poll()窃取了T5 → CAS失败
重试: s-1=3 → 取T4
4. poll窃取任务
当前: top=5, base=2
数组: [_, _, T3, T4, T5, _, _, _]
poll():
读取: b=2, s=5
检查: b < s ✓ (队列非空)
索引: 2 & 7 = 2
读取: array[2] = T3
CAS清空: array[2] = null ✓
更新: base = 3
结果: top=5, base=3, T3被窃取
poll()竞争场景:
读取: b=2, s=5
索引2: 读取T3
检查: base == b? (检查期间base是否变化)
如果base变为3 → 放弃,重新开始
否则CAS → 成功则base=3
5 top/base持续增长溢出
索引位计算与扩容判断(top与base差值),即便Int溢出也不影响数组运作
初始: top=0, base=0
//长期运行后:
top = 2147483640 (0x7FFFFFF8,接近MAX_VALUE)
base = 2147483633 (0x7FFFFFF1)
逻辑长度 = top - base = 7
二进制:0x7FFFFFF8 - 0x7FFFFFF1 = 0x00000007
//溢出:
几轮push()后:
push 后:top++ 溢出变成 -2147483648 (MIN_VALUE, 0x80000000)
base = 2147483633 (0x7FFFFFF1)
逻辑长度计算:
-base的补码 = ~base + 1 = 0x8000000F
top - base = top + (-base)
= 0x80000000 + 0x8000000F
= 0x10000000F ← 33位,溢出32位
截取低32位:0x0000000F = 15
//验证:
从 base=2147483633 到 top=2147483647(溢出前)有:
2147483647 - 2147483633 + 1 = 15个任务
+ 溢出后 top=-2147483648 代表一个新位置
总计 16 个逻辑位置
但实际只存了 15 个任务(从base到top-1)
所以长度 = 15,与二进制计算一致!
//状态判断的溢出安全性:
从上面的top与base的差值安全性可以推断出状态的安全性(队空、队满等)
//索引计算: top & mask
-2147483648 & 7 = 0 ✓
因为位运算只看低位,溢出不影响
6. 巧妙位运算
常规取模: index = pointer % length
位运算: index = pointer & (length-1)
效率对比:
% 运算: 需要除法指令,慢
& 运算: 单条位指令,极快
前提: length必须是2的幂
7. 队列状态判断
队列空: base >= top (瞬时快照)
队列非空: base < top
队列满: (top - base) >= (length-1)

8. 小结
这个双端队列设计的精妙之处在于:通过简单的整数运算和位操作,实现了高性能的无锁并发队列,完美支撑了工作窃取算法
- 无锁并发:通过volatile base + CAS实现线程安全
- 环形数组:
& mask替代取模,性能极致 - 双端分离:owner操作top,stealer操作base,减少竞争
- 安全边界:预留一个空位,避免满/空状态歧义
- 溢出容忍:int溢出不影响逻辑,长期运行稳定
- 动态扩容:按需2倍扩容,平衡内存与性能
9. Deque应用建议
| 场景 | 选择原因 | Java实现推荐 |
|---|---|---|
| 需要LIFO和FIFO混合 | 如ForkJoinPool的工作窃取 | ArrayDeque |
| 滑动窗口问题 | 需要两端操作维护单调性 | ArrayDeque |
| 缓存淘汰 | LRU需要快速移动元素到头部 | LinkedHashMap(内部是双端链表) |
| 撤销/重做 | 天然的后进先出+前进后出 | ArrayDeque |
| 优先级混合队列 | 高优先级插队到头部 | ArrayDeque或LinkedList |
| 环形缓冲区 | 高性能,避免数据移动 | 自定义环形数组 |
ArrayDeque:大多数场景首选,基于环形数组,性能好LinkedList:需要频繁在中间插入删除时使用ConcurrentLinkedDeque:高并发场景LinkedBlockingDeque:需要阻塞操作的生产者消费者
双端队列的核心价值在于提供了O(1)时间的两端操作,这在很多算法和系统设计中能带来显著的性能提升。理解其底层原理(如ForkJoinPool中的实现)能帮助我们在实际开发中做出更优的设计决策
线程调度
工作线程调度核心机制
1. 本地队列操作(Local Queue)
// 每个工作线程都有自己的双端队列(deque)
ArrayDeque<ForkJoinTask<?>> deque = workQueue;
// 从头部push/pop(LIFO) - 线程自己的任务
deque.push(task); // 内部fork的任务
deque.pop(); // 最近添加的任务优先执行(提高缓存局部性)
2. 扫描(Scanning)
// 工作线程执行流程
protected void runWorker(WorkQueue w) {
w.growArray(); // 初始化队列
// 扫描循环
while (scan(w, r) >= 0) { // r = 随机种子
// 从以下位置按顺序扫描:
// 1. 自己的队列(pop)
// 2. 其他队列的尾部(poll - FIFO)
// 3. 尝试窃取
}
}
3. 工作窃取(Work Stealing)
// 窃取算法核心逻辑
ForkJoinTask<?> stealWork(WorkQueue w) {
int r = ThreadLocalRandom.nextSecondarySeed();
int k = r & m; // m = 队列数-1
// 从其他队列的尾部窃取(FIFO)
// 窃取的是"最老"的任务,减少竞争
ForkJoinTask<?> t = queues[k].poll();
if (t != null) {
w.currentSteal = t;
return t;
}
return null;
}
4. 挂起与唤醒(Park/Unpark)
// 线程挂起条件:
// 1. 自己队列为空
// 2. 窃取失败
// 3. 处于非活跃状态
// 使用 LockSupport 挂起
LockSupport.park(this);
// 唤醒时机:
// 1. 外部提交新任务
// 2. 其他线程fork子任务
// 3. 窃取到任务
外部提交 vs 内部Fork
1. 外部任务提交(External Submission)
ForkJoinPool pool = new ForkJoinPool(4);
// 方式1:execute - 异步执行
pool.execute(new RecursiveTask() {
protected Object compute() {
// 任务逻辑
}
});
// 方式2:submit - 返回Future
Future<Integer> future = pool.submit(new RecursiveTask<Integer>() {
protected Integer compute() {
return 42;
}
});
// 方式3:invoke - 同步等待结果
Integer result = pool.invoke(task);
2. 内部Fork(Internal Fork)
class MyTask extends RecursiveTask<Integer> {
protected Integer compute() {
if (阈值条件) {
// 1. fork() - 异步执行子任务
MyTask left = new MyTask(...);
left.fork(); // 压入当前线程的队列
MyTask right = new MyTask(...);
right.fork();
// 2. join() - 等待结果
return left.join() + right.join();
// 或者使用 invokeAll() 简化
// invokeAll(left, right);
}
return 直接计算结果();
}
}
3. 关键区别对比表
| 特性 | 外部提交 | 内部Fork |
|---|---|---|
| 提交者 | 外部线程(main或非ForkJoin线程) | ForkJoinPool的工作线程 |
| 任务位置 | 提交到随机队列的尾部(外部提交队列) | 压入当前工作线程自己队列的头部 |
| 队列类型 | 共享的"提交队列"(submission queue) | 线程专属的双端队列(deque) |
| 调度优先级 | 较低(可能被窃取) | 较高(LIFO,优先执行) |
| 任务关系 | 独立任务 | 父子任务,有依赖关系 |
| join()行为 | 无(外部Future.get()) | 可能触发工作窃取(help-stealing) |
执行流程图解
外部提交任务 → 放入随机队列的尾部 → 唤醒空闲线程
↓
工作线程唤醒 → 从自己队列头部取任务(LIFO)
↓
遇到 fork() → 子任务压入自己队列头部
↓
遇到 join() →
├→ 子任务已完成:直接返回结果
├→ 子任务未开始:窃取并执行它
└→ 子任务执行中:从其他队列窃取任务
↓
队列为空 → 尝试从其他队列尾部窃取(FIFO)
↓
窃取失败 → 挂起线程(park)
总结
ForkJoinPool 源码极其复杂,特别是线程与任务调度模块,不建议细看。掌握分治思想与其并发控制设计对于开发者来说更重要。
- long变量存储多个状态(位运算)
- volatile + CAS(无锁更新)
- 工作窃取的双端队列(自己用LIFO,别人偷用FIFO)
- fork/invokeAll/join的正确用法
1. long记录多状态(位运算)
long ctl = ...;
// ctl的64位被分成多个部分:
// 高16位: 池状态 (活跃线程数、是否关闭等)
// 低48位: 其他信息
// 1. 状态读取(掩码+移位)
int runState = (int)(ctl >>> 48); // 获取高16位
int workerCount = (int)(ctl & 0x0000FFFFFFFFFFFFL); // 获取低48位
// 2. 状态更新(位运算)
long nc = ((long)newState << 48) | (workerCount & 0x0000FFFFFFFFFFFFL);
记住这个模式:
- 一个long,多个状态(内存高效,原子更新)
- 位掩码提取(& 操作)
- 移位组装(<< 和 | 操作)
- CAS更新(保证原子性)
2. volatile + CAS 核心模式
// ForkJoinPool中随处可见这个模式
public class ForkJoinPool {
private volatile long ctl; // volatile保证可见性
// CAS更新
boolean compareAndSetCtl(long expect, long update) {
return UNSAFE.compareAndSwapLong(this, CTL, expect, update);
}
// 典型使用场景
boolean addWorker() {
long c;
do {
c = ctl; // 读取volatile变量
// 计算新状态...
long nc = c + 1;
} while (!compareAndSetCtl(c, nc)); // CAS循环
return true;
}
}
3. 快速失败(Fail-Fast)模式
// 1. 参数检查快速失败
public void submit(ForkJoinTask<?> task) {
if (task == null) throw new NullPointerException();
if (isShutdown()) throw new RejectedExecutionException(); // 快速失败
// ... 正常逻辑
}
// 2. 状态检查快速失败
boolean trySteal() {
if (queue.isEmpty()) return false; // 快速失败,避免不必要的计算
// ... 尝试窃取逻辑
}
// 3. CAS失败快速重试
void pushTask(ForkJoinTask<?> task) {
WorkQueue q;
if ((q = workQueue) != null) { // 快速检查
int s = q.top;
if (q.array != null) { // 再次快速检查
// CAS更新
if (U.compareAndSwapObject(q.array, (q.array.length - 1) & s, null, task)) {
// 成功
} else {
// 失败,可能重试或采用其他策略
}
}
}
}
4. 工作窃取的核心模式(简化版)
// 这才是工作窃取的本质
class WorkStealingWorker {
Deque<Task> localQueue; // 自己的任务队列(LIFO)
Task getTask() {
// 1. 从自己队列取(优先处理最新的,提高局部性)
Task t = localQueue.pollLast(); // LIFO
if (t != null) return t;
// 2. 尝试从其他队列窃取(FIFO,减少竞争)
for (WorkStealingWorker other : allWorkers) {
if (other == this) continue;
t = other.stealTask(); // 从其他队列头部窃取
if (t != null) return t;
}
// 3. 都没有,挂起或执行其他工作
return null;
}
// 窃取方法(被其他线程调用)
synchronized Task stealTask() {
return localQueue.pollFirst(); // 从头部窃取(FIFO)
}
}

2318

被折叠的 条评论
为什么被折叠?



