需谨慎判断是否真需手动拆分,仅当任务明显可分割且默认并行流不满足性能或控制要求时才自定义拆分;优先用ForkJoinTask或ExecutorService,注意线程安全、结果聚合与异常处理。

任务拆分前先判断是否真需要手动拆分
Java 的 ForkJoinPool 和 CompletableFuture 已经对多数并行场景做了良好封装,盲目手动拆分反而引入线程安全、结果聚合、异常传播等额外复杂度。只有当任务具备明显可分割性(如处理超大数组、分页查询、独立文件批量处理),且默认并行流无法满足性能或控制粒度要求时,才考虑自定义拆分。
- 用
Arrays.parallelSort()或list.parallelStream().map(...).collect(...)能解决的,别写Thread子类 - 拆分后每个子任务耗时应远大于线程调度开销(通常 > 10ms),否则并发反而更慢
- 避免按“固定数量”硬拆(如每 100 条一组),优先按“估算工作量”拆(如每组预估执行 50ms)
用 ForkJoinTask 自定义可拆分任务
ForkJoinTask 是 JDK 提供的轻量级可拆分任务抽象,配合 ForkJoinPool.commonPool() 使用最简洁。关键在重写 compute():当任务过大就调用 fork() 拆,足够小就直接 compute() 执行。
class SumTask extends RecursiveTask{ final long[] array; final int lo, hi; SumTask(long[] array, int lo, int hi) { this.array = array; this.lo = lo; this.hi = hi; } protected Long compute() { if (hi - lo <= 1000) { // 拆分阈值需实测调整 long sum = 0; for (int i = lo; i < hi; i++) sum += array[i]; return sum; } int mid = (lo + hi) / 2; SumTask left = new SumTask(array, lo, mid); SumTask right = new SumTask(array, mid, hi); left.fork(); // 异步提交左子任务 long rightResult = right.compute(); // 当前线程算右子任务 long leftResult = left.join(); // 等待左子任务结果 return leftResult + rightResult; } }
- 不要在
compute()中调用fork()后立刻join(),这等于串行执行 -
ForkJoinPool默认使用工作窃取(work-stealing),但若任务有强顺序依赖(如必须先处理 A 再 B),需改用CountDownLatch或CyclicBarrier - 拆分逻辑必须幂等——同一任务多次执行不能改变系统状态
用 ExecutorService + Callable 实现可控粒度拆分
当需要精确控制线程数、拒绝策略或任务生命周期时,ExecutorService 比 ForkJoinPool 更合适。典型做法是将原始数据切片为 List,再用 invokeAll() 批量提交。
List> tasks = new ArrayList<>(); int chunkSize = (data.size() + threadCount - 1) / threadCount; for (int i = 0; i < data.size(); i += chunkSize) { int end = Math.min(i + chunkSize, data.size()); tasks.add(new DataProcessor(data.subList(i, end))); } List > futures = executor.invokeAll(tasks);
-
subList()返回的是原List的视图,多线程修改会引发ConcurrentModificationException,务必先复制(如new ArrayList(original.subList(...))) -
invokeAll()会阻塞直到所有任务完成,若某任务抛异常,对应Future.get()会抛ExecutionException,需显式捕获 - 线程池大小不建议硬编码为
Runtime.getRuntime().availableProcessors(),IO 密集型任务通常需要更大线程数
结果聚合与异常处理最容易被忽略
拆分后任务的结果不是自动合并的,异常也不会自动向上冒泡。常见错误是只检查 Future.isDone() 却忽略 Future.get() 可能抛出的异常。
立即学习“Java免费学习笔记(深入)”;
- 用
CompletableFuture.allOf()组合多个异步任务时,它只表示“全部完成”,不包含结果;要取结果得遍历原始CompletableFuture[]并调用join() - 若某子任务失败,整个聚合结果可能无效,需提前约定失败策略:是跳过(
exceptionally())、重试(handle()),还是中断全部(cancel(true)) - 避免在子任务中直接操作共享集合(如
ArrayList),改用ConcurrentLinkedQueue或收集到局部 List 后由主线程合并
真正难的从来不是怎么拆,而是拆完之后怎么让各部分不互相干扰、出错时知道哪块坏了、结果出来后能对得上原始顺序——这些细节比拆分逻辑本身更消耗调试时间。







