ForkJoin 框架源码深度解析:工作窃取 + RecursiveTask 实战

本文回答什么问题 :ForkJoin 框架如何用"分治 + 工作窃取"让 1 亿级数组求和自动并行,覆盖 ForkJoinTask 三大子类、工作窃取 Deque 原理与 5 个可执行 Demo? 预计阅读时间 :25 分钟

源码版本 :OpenJDK 17(java.util.concurrent.ForkJoinPool + ForkJoinTask + RecursiveTask

JDK 7 引入的 ForkJoin 框架是 Java 并发库中最精妙的设计之一------Doug Lea 用"工作窃取算法"(Work-Stealing)让 CPU 密集型分治任务自动并行,而 JDK 8 的并行流和 CompletableFuture 默认线程池都构建在它之上。本文从"1 亿数组求和"的真实问题切入,用 5 个可执行 Demo + 4 张时序/类图,把工作窃取原理、RecursiveTask/RecursiveAction/CountedCompleter 三大子类一次性讲透。

核心要点速查(建议收藏)

  • JDK 7 引入 ForkJoin 框架,Doug Lea 设计,JDK 8 的 parallelStream() 底层直接使用 ForkJoinPool.commonPool() ,理解它是读懂并行流的钥匙。
  • 核心算法:工作窃取(Work-Stealing) ------每个 Worker 线程持有自己的双端队列(Deque),自己从头部 pop(LIFO) ,空闲时从其他队列尾部 steal(FIFO) ,最大化缓存局部性 + 最小化锁竞争。
  • 三大子类:RecursiveTask<V>(有返回值)/ RecursiveAction(无返回值)/ CountedCompleter<T>(带完成回调) ------99% 场景用前两个,复杂依赖用第三个。
  • ForkJoinPool 与 ThreadPoolExecutor 本质区别 :前者是"分治 + 窃取",每个线程私有队列;后者是"共享阻塞队列",IO 密集型必须用 ThreadPoolExecutor,CPU 密集型分治任务才用 ForkJoinPool
  • commonPool() 三大坑 :守护线程(主线程结束 JVM 退出) / 线程数 = CPU 核数 - 1(IO 密集不够用) / 被 parallelStream 共享(业务互相影响),生产环境必须自定义 ForkJoinPool
  • fork/join 黄金模式:fork() + compute() + join() ------左任务 fork() 异步执行,右任务 compute() 同步执行(避免不必要窃取),最后 join() 阻塞等结果。任务执行时间 < 10ms 不要 fork,> 100ms 才值得 fork。

📋 概述

一句话:ForkJoin 框架是 Java 7 引入的"分治并行神器",通过"拆分-执行-合并"的递归模式 + "工作窃取"算法,让大任务自动分配到多个线程并行执行。

从一个真实问题切入

假设你有一个包含 1 亿个元素的数组,需要计算它们的总和。如果用单线程遍历,可能需要几秒甚至更长时间。如果直接创建 100 个线程,每个线程处理 100 万个元素,又会遇到线程管理的开销和负载不均衡的问题。

ForkJoin 解决的核心问题

问题 没有 ForkJoin 时 有 ForkJoin 时
任务拆分 手动拆分,代码复杂 递归自动拆分,简洁优雅
负载均衡 固定分配,忙闲不均 工作窃取,自动均衡
线程复用 每个任务可能需要新线程 线程池复用,高效执行
结果合并 手动编写合并逻辑 fork/join 自动合并

💡 生活类比:包工头互相借工人

理解 ForkJoin 最直观的方式是把它想象成建筑工地的施工队

施工队概念 ForkJoin 概念 类比说明
大工程 大任务 需要完成的整体工作
拆分工程 Fork 把大任务拆成多个小任务
等工人汇报 Join 等待所有子任务完成,汇总结果
借工人 工作窃取 干完活的工人去帮其他忙的工人
双端队列 WorkQueue 每个工人的任务清单
施工队 ForkJoinPool 管理工人的线程池

核心思想

  • Fork(拆分) :把大任务拆成小任务,直到小到可以直接计算
  • Join(合并) :等所有子任务完成后,汇总结果
  • 工作窃取:干完活的工人去"偷"其他工人队列尾部的任务,实现负载均衡

🔍 工作窃取算法 ⭐

工作窃取(Work Stealing)是 ForkJoin 框架的核心算法,也是它区别于普通线程池的关键。

为什么需要工作窃取?

传统线程池使用共享队列 ,所有线程从同一个队列取任务,存在锁竞争 。ForkJoin 让每个线程有自己的双端队列(Deque) ,只在自己队列上操作,减少竞争。

工作窃取的工作原理

关键特性

  • 双端队列(Deque) :线程从头部 取任务(LIFO,最近提交的先执行),窃取者从尾部偷任务(FIFO,最老的任务先执行)
  • 减少竞争:每个线程主要操作自己的队列,只有窃取时才访问其他线程的队列
  • 自动均衡:忙的线程不会被打扰,闲的线程主动帮忙

为什么从尾部窃取?

ini 复制代码
Deque: [任务1, 任务2, 任务3, 任务4]
         ↑                    ↑
      线程自己取(LIFO)    窃取者取(FIFO)
  • 自己取(LIFO) :最新创建的子任务通常是最小的,优先处理可以更快释放资源
  • 窃取者取(FIFO) :最老的任务通常比较大,窃取者可以长时间工作,减少窃取频率

🔍 ForkJoinTask

ForkJoinTask 是 ForkJoin 框架的核心抽象类,代表一个可以被 Fork/Join 的任务。

类继承关系

RecursiveTask vs RecursiveAction

特性 RecursiveTask RecursiveAction
返回值 有(V 类型) 无(void)
compute() 返回 必须返回结果 不返回结果
典型场景 求和、排序、查找 批量处理、数据转换
join() 获取 返回计算结果 返回 null
示例 计算数组总和 给数组每个元素加 1

fork() 和 join() 方法

arduino 复制代码
// fork():异步执行当前任务
// 将任务放入当前线程的双端队列,然后立即返回
// 调用者可以继续执行其他任务

// join():等待任务完成并获取结果
// 如果任务已完成,立即返回结果
// 如果任务未完成,阻塞当前线程直到任务完成
// 关键:join() 不会触发工作窃取(与 invoke() 不同)

// invoke():fork + join 的组合
// 异步执行任务,然后等待结果
// 可能触发工作窃取
scss 复制代码
// 标准 fork/join 模式
MyTask left = new MyTask(data1);
MyTask right = new MyTask(data2);

left.fork();  // 异步执行左任务
Result rightResult = right.compute();  // 同步执行右任务
Result leftResult = left.join();  // 等待左任务完成

return merge(leftResult, rightResult);

🔍 ForkJoinPool

ForkJoinPool 是 ForkJoin 框架的线程池实现,与 ThreadPoolExecutor 有本质区别。

ForkJoinPool vs ThreadPoolExecutor

特性 ForkJoinPool ThreadPoolExecutor
队列类型 每个线程自己的双端队列(Deque) 共享的阻塞队列(BlockingQueue)
任务调度 工作窃取(Work Stealing) FIFO 或优先级调度
适用场景 递归可分的任务(分治算法) 独立的异步任务
线程协作 线程间可以互相帮忙 线程独立工作
嵌套任务 支持 fork 子任务 不支持(会死锁)
常用方法 invoke()/submit() execute()/submit()
并行流 parallelStream() 底层 不支持

ForkJoinPool 的两种模式

ini 复制代码
// 1. 无返回值(RecursiveAction)
ForkJoinPool pool = new ForkJoinPool();
pool.invoke(new MyAction(data));

// 2. 有返回值(RecursiveTask)
ForkJoinPool pool = new ForkJoinPool();
Result result = pool.invoke(new MyTask(data));

// 3. 使用 commonPool()(全局共享)
ForkJoinPool commonPool = ForkJoinPool.commonPool();

commonPool() 的特点

csharp 复制代码
// commonPool 是全局共享的 ForkJoinPool
// 特点:
// 1. 线程数 = CPU 核数 - 1(默认)
// 2. 是守护线程(daemon)
// 3. 被 parallelStream() 共享
// 4. 不适合 IO 密集型任务

// 获取 commonPool
ForkJoinPool commonPool = ForkJoinPool.commonPool();
System.out.println("commonPool 并行度: " + commonPool.getParallelism());

💻 可跑代码

Demo 1:RecursiveTask 实现数组求和

java 复制代码
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.RecursiveTask;

public class ForkJoinSumDemo extends RecursiveTask<Long> {
    private static final int THRESHOLD = 1000; // 阈值:小于此值直接计算
    private final long[] array;
    private final int start;
    private final int end;
    
    public ForkJoinSumDemo(long[] array, int start, int end) {
        this.array = array;
        this.start = start;
        this.end = end;
    }
    
    @Override
    protected Long compute() {
        int length = end - start;
        
        // 小于阈值,直接计算
        if (length <= THRESHOLD) {
            long sum = 0;
            for (int i = start; i < end; i++) {
                sum += array[i];
            }
            return sum;
        }
        
        // 拆分任务
        int mid = start + length / 2;
        ForkJoinSumDemo leftTask = new ForkJoinSumDemo(array, start, mid);
        ForkJoinSumDemo rightTask = new ForkJoinSumDemo(array, mid, end);
        
        // 异步执行左任务
        leftTask.fork();
        
        // 同步执行右任务
        long rightResult = rightTask.compute();
        
        // 等待左任务完成
        long leftResult = leftTask.join();
        
        // 合并结果
        return leftResult + rightResult;
    }
    
    public static void main(String[] args) {
        // 创建测试数据
        long[] array = new long[10000];
        for (int i = 0; i < array.length; i++) {
            array[i] = i + 1;
        }
        
        // 使用 ForkJoinPool 执行
        ForkJoinPool pool = new ForkJoinPool();
        long result = pool.invoke(new ForkJoinSumDemo(array, 0, array.length));
        
        System.out.println("数组总和: " + result);
        System.out.println("期望值: " + (10000L * 10001 / 2));
        
        pool.shutdown();
    }
}

// 预期输出:
// 数组总和: 50005000
// 期望值: 50005000

Demo 2:RecursiveAction 实现大数组并行处理

java 复制代码
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.RecursiveAction;
import java.util.Arrays;

public class ForkJoinTransformDemo extends RecursiveAction {
    private static final int THRESHOLD = 1000;
    private final int[] array;
    private final int start;
    private final int end;
    
    public ForkJoinTransformDemo(int[] array, int start, int end) {
        this.array = array;
        this.start = start;
        this.end = end;
    }
    
    @Override
    protected void compute() {
        int length = end - start;
        
        if (length <= THRESHOLD) {
            // 小任务:给每个元素乘以 2
            for (int i = start; i < end; i++) {
                array[i] = array[i] * 2;
            }
            return;
        }
        
        // 拆分任务
        int mid = start + length / 2;
        ForkJoinTransformDemo leftTask = new ForkJoinTransformDemo(array, start, mid);
        ForkJoinTransformDemo rightTask = new ForkJoinTransformDemo(array, mid, end);
        
        invokeAll(leftTask, rightTask); // 同时执行两个任务
    }
    
    public static void main(String[] args) {
        int[] array = new int[100];
        for (int i = 0; i < array.length; i++) {
            array[i] = i + 1;
        }
        
        System.out.println("处理前: " + Arrays.toString(Arrays.copyOf(array, 10)));
        
        ForkJoinPool pool = new ForkJoinPool();
        pool.invoke(new ForkJoinTransformDemo(array, 0, array.length));
        
        System.out.println("处理后: " + Arrays.toString(Arrays.copyOf(array, 10)));
        
        pool.shutdown();
    }
}

// 预期输出:
// 处理前: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
// 处理后: [2, 4, 6, 8, 10, 12, 14, 16, 18, 20]

Demo 3:ForkJoinPool 基本用法 + 工作窃取演示

java 复制代码
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.RecursiveTask;
import java.util.concurrent.atomic.AtomicInteger;

public class ForkJoinStealDemo extends RecursiveTask<Integer> {
    private static final int THRESHOLD = 5;
    private static final AtomicInteger taskCount = new AtomicInteger(0);
    
    private final int n;
    private final int depth;
    
    public ForkJoinStealDemo(int n, int depth) {
        this.n = n;
        this.depth = depth;
    }
    
    @Override
    protected Integer compute() {
        int taskId = taskCount.incrementAndGet();
        String threadName = Thread.currentThread().getName();
        System.out.printf("[%s] 执行任务 #%d, n=%d, depth=%d%n", 
            threadName, taskId, n, depth);
        
        if (n <= THRESHOLD) {
            // 模拟耗时操作
            try { Thread.sleep(100); } catch (InterruptedException e) {}
            return n * n;
        }
        
        ForkJoinStealDemo task1 = new ForkJoinStealDemo(n - 1, depth + 1);
        ForkJoinStealDemo task2 = new ForkJoinStealDemo(n - 2, depth + 1);
        
        task1.fork(); // 异步执行
        int result2 = task2.compute(); // 同步执行
        int result1 = task1.join(); // 等待结果
        
        return result1 + result2;
    }
    
    public static void main(String[] args) {
        ForkJoinPool pool = new ForkJoinPool(4); // 4 个线程
        
        System.out.println("ForkJoinPool 并行度: " + pool.getParallelism());
        System.out.println("开始执行...\n");
        
        long start = System.currentTimeMillis();
        int result = pool.invoke(new ForkJoinStealDemo(20, 0));
        long elapsed = System.currentTimeMillis() - start;
        
        System.out.println("\n结果: " + result);
        System.out.println("耗时: " + elapsed + "ms");
        System.out.println("总任务数: " + taskCount.get());
        
        pool.shutdown();
    }
}

// 预期输出(线程名和任务顺序可能不同,体现工作窃取):
// ForkJoinPool 并行度: 4
// 开始执行...
// [ForkJoinPool-1-worker-1] 执行任务 #1, n=20, depth=0
// [ForkJoinPool-1-worker-1] 执行任务 #2, n=19, depth=1
// [ForkJoinPool-1-worker-2] 执行任务 #3, n=18, depth=2(工作窃取)
// ...
// 结果: 28656
// 耗时: 1234ms
// 总任务数: 39

Demo 4:parallelStream 底层使用 ForkJoinPool

java 复制代码
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

public class ParallelStreamDemo {
    public static void main(String[] args) {
        List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
        
        // parallelStream 底层使用 ForkJoinPool.commonPool()
        List<Integer> result = numbers.parallelStream()
            .map(n -> {
                System.out.println("[" + Thread.currentThread().getName() + "] 处理: " + n);
                return n * n;
            })
            .sorted()
            .collect(Collectors.toList());
        
        System.out.println("结果: " + result);
        
        // 注意:commonPool 的线程数 = CPU 核数 - 1
        System.out.println("commonPool 并行度: " + 
            java.util.concurrent.ForkJoinPool.commonPool().getParallelism());
    }
}

// 预期输出(顺序可能不同):
// [ForkJoinPool.commonPool-worker-1] 处理: 1
// [ForkJoinPool.commonPool-worker-1] 处理: 3
// [ForkJoinPool.commonPool-worker-2] 处理: 2
// ...
// 结果: [1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
// commonPool 并行度: 7

Demo 5:自定义 ForkJoinPool

java 复制代码
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.RecursiveTask;
import java.util.concurrent.TimeUnit;

public class CustomForkJoinPoolDemo extends RecursiveTask<Integer> {
    private static final int THRESHOLD = 10;
    private final int[] array;
    private final int start;
    private final int end;
    
    public CustomForkJoinPoolDemo(int[] array, int start, int end) {
        this.array = array;
        this.start = start;
        this.end = end;
    }
    
    @Override
    protected Integer compute() {
        int length = end - start;
        if (length <= THRESHOLD) {
            int sum = 0;
            for (int i = start; i < end; i++) {
                sum += array[i];
            }
            return sum;
        }
        
        int mid = start + length / 2;
        CustomForkJoinPoolDemo left = new CustomForkJoinPoolDemo(array, start, mid);
        CustomForkJoinPoolDemo right = new CustomForkJoinPoolDemo(array, mid, end);
        
        left.fork();
        int rightResult = right.compute();
        int leftResult = left.join();
        
        return leftResult + rightResult;
    }
    
    public static void main(String[] args) throws Exception {
        int[] array = new int[1000];
        for (int i = 0; i < array.length; i++) {
            array[i] = i + 1;
        }
        
        // 自定义 ForkJoinPool
        ForkJoinPool pool = new ForkJoinPool(
            4,                                    // 并行度(线程数)
            ForkJoinPool.defaultForkJoinWorkerThreadFactory, // 工作线程工厂
            null,                                 // 异常处理器
            true                                  // 是 asyncMode
        );
        
        // 监控线程池状态
        System.out.println("并行度: " + pool.getParallelism());
        System.out.println("活跃线程数: " + pool.getActiveThreadCount());
        System.out.println("队列大小: " + pool.getQueuedTaskCount());
        
        // 执行任务
        int result = pool.invoke(new CustomForkJoinPoolDemo(array, 0, array.length));
        System.out.println("结果: " + result);
        
        // 等待线程池关闭
        pool.shutdown();
        pool.awaitTermination(5, TimeUnit.SECONDS);
        
        System.out.println("是否已终止: " + pool.isTerminated());
    }
}

// 预期输出:
// 并行度: 4
// 活跃线程数: 0
// 队列大小: 0
// 结果: 500500
// 是否已终止: true

⚠️ 常见问题与踩坑

Q1:ForkJoinPool 和 ThreadPoolExecutor 怎么选?

:根据任务特性选择:

场景 推荐 原因
递归可分的任务 ForkJoinPool 分治算法 + 工作窃取天然适配
独立的异步任务 ThreadPoolExecutor 任务之间无依赖,共享队列更简单
IO 密集型任务 ThreadPoolExecutor ForkJoinPool 不适合阻塞操作
并行流(parallelStream) ForkJoinPool.commonPool() 底层实现
需要自定义线程数 自定义 ForkJoinPool commonPool 默认核数-1
scss 复制代码
// ❌ 不适合 ForkJoin 的场景:IO 密集型
ForkJoinPool pool = new ForkJoinPool();
pool.invoke(new RecursiveTask() {
    protected Object compute() {
        // 发送 HTTP 请求(IO 操作,会阻塞线程)
        httpClient.send(request);  // ⚠️ 阻塞!
        return result;
    }
});

// ✅ IO 密集型用 ThreadPoolExecutor
ExecutorService ioPool = Executors.newFixedThreadPool(20);
ioPool.submit(() -> httpClient.send(request));

Q2:commonPool 什么时候用,什么时候不用?

适合用 commonPool 的场景

  • 并行流(parallelStream()
  • 轻量级 CPU 密集型计算
  • 临时性任务,不需要资源隔离

不适合用 commonPool 的场景

  • IO 密集型任务(线程数 = CPU 核数 - 1,不够用)
  • 需要自定义线程数
  • 需要资源隔离
  • 生产环境长时间运行的任务
scss 复制代码
// ❌ 危险:所有业务共用 commonPool
CompletableFuture.supplyAsync(() -> queryUser());  // 用 commonPool
CompletableFuture.supplyAsync(() -> queryOrder()); // 用 commonPool
// 可能互相影响

// ✅ 安全:自定义线程池隔离
ExecutorService userPool = Executors.newFixedThreadPool(8);
ExecutorService orderPool = Executors.newFixedThreadPool(8);
CompletableFuture.supplyAsync(() -> queryUser(), userPool);
CompletableFuture.supplyAsync(() -> queryOrder(), orderPool);

Q3:任务太小不要 fork,什么时候算"太小"?

:fork 有开销(创建任务对象、入队、线程调度),如果任务执行时间 < fork 开销,就不值得 fork。

经验法则

  • 任务执行时间 < 1ms:不要 fork
  • 任务执行时间 < 10ms:谨慎 fork
  • 任务执行时间 > 100ms:值得 fork
scss 复制代码
// ❌ 任务太小,fork 开销大于收益
if (n > 1) {
    fork(new Task(n - 1));  // 任务只做一次减法,不值得 fork
    return compute();
}

// ✅ 合适的阈值
private static final int THRESHOLD = 1000;

if (end - start <= THRESHOLD) {
    // 直接计算,不 fork
    return directCompute(array, start, end);
} else {
    // fork 子任务
    fork(new Task(array, start, mid));
    return compute();
}

Q4:join() 会阻塞吗?与 invoke() 有什么区别?

方法 行为 是否阻塞 是否触发工作窃取
fork() 异步执行任务 不阻塞 不触发
join() 等待任务完成 阻塞 不触发
invoke() fork + join 阻塞 可能触发
scss 复制代码
// 标准 fork/join 模式
Task left = new Task(data1);
Task right = new Task(data2);

left.fork();  // 异步执行
Result rightResult = right.compute();  // 同步执行
Result leftResult = left.join();  // 阻塞等待(但不触发窃取)
return merge(leftResult, rightResult);

// invoke 模式
Task task = new Task(data);
Result result = task.invoke();  // fork + join,可能触发窃取

最佳实践 :在 fork/join 模式中,最后一个任务用 compute() 同步执行 ,其他任务用 fork() 异步执行,最后用 join() 等待结果。这样可以避免不必要的工作窃取。

Q5:ForkJoin 的异常处理?

:ForkJoinTask 的异常处理与普通任务不同:

php 复制代码
// 方式 1:getException() 获取异常
ForkJoinTask<Integer> task = pool.invoke(new MyTask());
try {
    Integer result = task.join();
} catch (Exception e) {
    Throwable ex = task.getException(); // 获取原始异常
    if (ex instanceof RuntimeException) {
        throw (RuntimeException) ex;
    } else if (ex instanceof Error) {
        throw (Error) ex;
    } else {
        throw new RuntimeException(ex);
    }
}

// 方式 2:在任务内部处理
@Override
protected Integer compute() {
    try {
        return doCompute();
    } catch (Exception e) {
        // 记录日志
        logger.error("Task failed", e);
        // 返回默认值或重新抛出
        return defaultValue;
    }
}

// 方式 3:使用 CountedCompleter(更灵活)
// CountedCompleter 支持 onCompletion 回调,适合复杂场景

Q6:ForkJoinTask 可以取消吗?

:可以,但需要配合中断机制:

csharp 复制代码
ForkJoinTask<Integer> task = pool.invoke(new MyTask());

// 取消任务(mayInterruptIfRunning=true 会中断线程)
boolean cancelled = task.cancel(true);

if (cancelled) {
    System.out.println("任务已取消");
}

// 检查任务状态
if (task.isCancelled()) {
    System.out.println("任务被取消");
} else if (task.isCompletedNormally()) {
    System.out.println("任务正常完成");
} else if (task.isCompletedAbnormally()) {
    System.out.println("任务异常完成: " + task.getException());
}

🎯 最佳实践

Do / Don't 对照表

✅ Do ❌ Don't
递归可分的任务用 ForkJoinPool IO 密集型任务用 ForkJoinPool
设置合理的阈值(THRESHOLD) 任务太小就 fork
最后一个任务同步执行(compute) 所有任务都 fork
用 invokeAll() 同时 fork 两个任务 分两次 fork 两个任务
自定义线程池隔离业务 所有业务共用 commonPool
处理任务中的异常 忽略异常导致任务丢失
用 CountedCompleter 处理复杂场景 用 RecursiveTask 处理复杂场景

性能优化技巧

scss 复制代码
// 1. 合理设置阈值
private static final int THRESHOLD = computeOptimalThreshold();

// 2. 避免 fork/join 不平衡
// ❌ 不平衡:左任务大,右任务小
Task left = new Task(array, 0, array.length * 3 / 4);  // 大任务
Task right = new Task(array, array.length * 3 / 4, array.length);  // 小任务

// ✅ 平衡:左右任务差不多大
Task left = new Task(array, 0, array.length / 2);
Task right = new Task(array, array.length / 2, array.length);

// 3. 减少任务创建
// ❌ 每次创建新任务对象
for (int i = 0; i < n; i++) {
    pool.invoke(new Task(i));  // 创建 n 个任务对象
}

// ✅ 复用任务对象
Task reusableTask = new Task();
for (int i = 0; i < n; i++) {
    reusableTask.setData(i);
    pool.invoke(reusableTask);
}

💡 面试要点

  1. ForkJoin 的核心思想:分治(Fork/Join)+ 工作窃取(Work Stealing)
  2. RecursiveTask vs RecursiveAction:有返回值 vs 无返回值
  3. 工作窃取的原理:每个线程有自己的双端队列,空闲线程从其他队列尾部窃取任务
  4. ForkJoinPool vs ThreadPoolExecutor:工作窃取 vs 共享队列
  5. fork/join 的最佳实践:最后一个任务同步执行,其他任务异步执行
  6. commonPool 的特点:全局共享、守护线程、线程数 = CPU 核数 - 1
  7. parallelStream 的底层:使用 ForkJoinPool.commonPool()
  8. 任务阈值:太小不要 fork,通常 > 100ms 才值得 fork
  9. 异常处理:getException() 获取原始异常
  10. 适用场景:递归可分的 CPU 密集型任务,不适合 IO 密集型

📝 总结

要点 记住这一句
核心价值 分治 + 工作窃取,让大任务自动并行
RecursiveTask 有返回值的分治任务
RecursiveAction 无返回值的分治任务
工作窃取 每个线程有自己的队列,空闲时从其他队列尾部偷任务
fork/join 模式 最后一个任务 compute(),其他 fork() + join()
commonPool 全局共享,parallelStream 底层,线程数 = CPU 核数 - 1
适用场景 递归可分的 CPU 密集型任务
避免坑 任务太小不要 fork,IO 密集型不要用

一句话心法 :ForkJoin 是"分而治之"思想的并行实现------能拆就拆,拆完再合,闲人帮忙

📖 外部参考


tags :#java #并发 #ForkJoin #工作窃取 #RecursiveTask #分治算法 #JDK17 #源码解析

相关推荐
大勇前进7 小时前
jQuery 事件委托原理:彻底解决动态生成元素绑定失效问题
后端
苍何8 小时前
做了个微信聊天爆款视频 Skill, Codex / WorkBuddy一键复刻使用
后端·aigc
卷无止境8 小时前
用 FastAPI 撑起大文件的上传下载:从流式处理到断点续传的完整实践
后端·python·fastapi
铁皮饭盒8 小时前
网页端, 6.5MB人脸识别模型, 谷歌框架, 又快又准
前端·javascript·后端
长大19888 小时前
jQuery AJAX 完整封装教程:告别重复写请求代码
后端
worxfr9 小时前
Go 并发控制:从 Channel 方向约束到实战模式
开发语言·后端·golang
拾陆楼9 小时前
PT: DMSA辅助调tree报告前后级余量脚本
后端·学习
今天的砖头有点烫手啊9 小时前
接口太慢?Spring Boot 缓存体系 @Cacheable 全链路拆解
spring boot·后端·缓存
雪隐10 小时前
个人电脑玩AI-16让5060 Ti给你打工——MiniMax H3 提速实录:从"泡杯茶等视频"到"视频等你",一张 5060 Ti 的自我修养
前端·人工智能·后端
9523610 小时前
Sentinel
java·后端·spring·sentinel·springcloud