You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Java数组并行处理疑问:并行执行耗时更高如何优化?

针对数组分段并行处理与结果合并的可行方案

嘿,我完全懂你的需求——重点不是追求算法的极致高效,而是要实打实地实现多线程并行处理数组不同分段、再合并结果的目标。你碰到Stream并行比串行慢的问题,多半是因为Stream并行调度的额外开销(比如线程切换、任务拆分成本),加上这个统计场景的计算粒度偏细,反而抵消了并行的优势。下面给你几个落地性强的方案:

一、自定义分段+线程池+局部结果合并

这个方案最直观,完全可控,能清晰实现“分段处理+结果聚合”的逻辑:

步骤说明

  1. 合理分段:根据CPU核心数(比如Runtime.getRuntime().availableProcessors())将2亿数组拆分成对应数量的分段,避免过多线程导致竞争,也避免负载不均。
  2. 局部统计:每个线程单独处理一段数组,用普通HashMap统计这段内元素的出现次数(单线程处理自己的分段,无并发竞争,不用ConcurrentHashMap,减少额外开销)。
  3. 结果合并:收集所有线程的局部统计Map,将相同元素的次数累加,最后遍历合并后的Map找出出现次数为奇数的元素。

代码示例

import java.util.*;
import java.util.concurrent.*;

public class ParallelArrayProcessor {
    private static final int ARRAY_SIZE = 200_000_000;
    private static final int THREAD_COUNT = Runtime.getRuntime().availableProcessors();

    public static void main(String[] args) throws InterruptedException, ExecutionException {
        int[] array = generateRandomArray(ARRAY_SIZE);

        // 初始化线程池
        ExecutorService executor = Executors.newFixedThreadPool(THREAD_COUNT);
        List<Future<Map<Integer, Integer>>> futures = new ArrayList<>();

        // 拆分任务并提交
        int segmentSize = ARRAY_SIZE / THREAD_COUNT;
        for (int i = 0; i < THREAD_COUNT; i++) {
            int start = i * segmentSize;
            int end = (i == THREAD_COUNT - 1) ? ARRAY_SIZE : (i + 1) * segmentSize;
            futures.add(executor.submit(new SegmentProcessor(array, start, end)));
        }

        // 合并局部结果
        Map<Integer, Integer> globalCount = new HashMap<>();
        for (Future<Map<Integer, Integer>> future : futures) {
            Map<Integer, Integer> localCount = future.get();
            localCount.forEach((key, count) -> globalCount.merge(key, count, Integer::sum));
        }

        // 找出奇数次出现的元素
        List<Integer> oddOccurrence = new ArrayList<>();
        globalCount.forEach((key, count) -> {
            if (count % 2 != 0) {
                oddOccurrence.add(key);
            }
        });

        executor.shutdown();
    }

    private static int[] generateRandomArray(int size) {
        Random random = new Random();
        int[] array = new int[size];
        for (int i = 0; i < size; i++) {
            array[i] = random.nextInt(1000); // 随机生成0-999的元素
        }
        return array;
    }

    static class SegmentProcessor implements Callable<Map<Integer, Integer>> {
        private final int[] array;
        private final int start;
        private final int end;

        public SegmentProcessor(int[] array, int start, int end) {
            this.array = array;
            this.start = start;
            this.end = end;
        }

        @Override
        public Map<Integer, Integer> call() {
            Map<Integer, Integer> localCount = new HashMap<>();
            for (int i = start; i < end; i++) {
                int num = array[i];
                localCount.put(num, localCount.getOrDefault(num, 0) + 1);
            }
            return localCount;
        }
    }
}

二、利用Fork/Join框架实现分治处理

Fork/Join是Java专门为分治任务设计的框架,它会自动拆分任务到合适的粒度,适合数组这种可拆分的数据集:

步骤说明

  1. 定义RecursiveTask:继承RecursiveTask<Map<Integer, Integer>>,实现任务拆分逻辑——当数组分段小于阈值(比如100万)时直接统计,否则拆分成左右两个子任务,fork执行后合并结果。
  2. 执行任务:通过ForkJoinPool提交任务,获取全局统计结果后再筛选奇数次数元素。

代码示例

import java.util.*;
import java.util.concurrent.*;

public class ForkJoinArrayProcessor {
    private static final int ARRAY_SIZE = 200_000_000;
    private static final int THRESHOLD = 1_000_000; // 任务拆分阈值

    public static void main(String[] args) {
        int[] array = generateRandomArray(ARRAY_SIZE);
        ForkJoinPool pool = new ForkJoinPool();
        Map<Integer, Integer> globalCount = pool.invoke(new CountTask(array, 0, array.length));

        // 筛选奇数次出现的元素
        List<Integer> oddOccurrence = new ArrayList<>();
        globalCount.forEach((key, count) -> {
            if (count % 2 != 0) {
                oddOccurrence.add(key);
            }
        });
    }

    private static int[] generateRandomArray(int size) {
        Random random = new Random();
        int[] array = new int[size];
        for (int i = 0; i < size; i++) {
            array[i] = random.nextInt(1000);
        }
        return array;
    }

    static class CountTask extends RecursiveTask<Map<Integer, Integer>> {
        private final int[] array;
        private final int start;
        private final int end;

        public CountTask(int[] array, int start, int end) {
            this.array = array;
            this.start = start;
            this.end = end;
        }

        @Override
        protected Map<Integer, Integer> compute() {
            if (end - start <= THRESHOLD) {
                // 小粒度任务直接统计
                Map<Integer, Integer> localCount = new HashMap<>();
                for (int i = start; i < end; i++) {
                    int num = array[i];
                    localCount.put(num, localCount.getOrDefault(num, 0) + 1);
                }
                return localCount;
            } else {
                // 拆分任务
                int mid = (start + end) / 2;
                CountTask leftTask = new CountTask(array, start, mid);
                CountTask rightTask = new CountTask(array, mid, end);
                leftTask.fork();
                Map<Integer, Integer> rightResult = rightTask.compute();
                Map<Integer, Integer> leftResult = leftTask.join();

                // 合并子任务结果
                rightResult.forEach((key, count) -> leftResult.merge(key, count, Integer::sum));
                return leftResult;
            }
        }
    }
}

关键优化点提醒

  • 分段大小/阈值设置:不要把分段拆得太小,否则线程切换开销会远大于并行收益;也不要太大,避免单个线程负载过重。比如CPU核心数为8时,2亿数组每段2500万的粒度比较合适。
  • 避免不必要的并发集合:局部统计用普通HashMap就行,只有合并阶段才需要处理全局的Map,减少锁开销。
  • 线程池大小:自定义线程池时,建议设置为CPU核心数(或者核心数+1),避免过多线程导致上下文切换。

内容的提问来源于stack exchange,提问作者Dhananjay

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.29 06:47:37