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

归并排序的并发计算实现咨询:已实现归并排序,如何使其并发运行?

嘿,很高兴帮你把归并排序改成并发版本!归并排序天生就适合并行化,因为它的拆分阶段完全是独立的——左右子数组的排序互相不依赖,刚好可以丢给不同线程去跑。下面我给你两种常见的实现方式,还有一些关键的注意点:

并发归并排序的核心思路

首先,归并排序的拆分步骤是并行化的黄金切入点:当你把数组拆成左右两半后,这两个子数组的排序操作完全独立,不需要互相等待,所以可以同时交给不同的线程处理。等两边都排好序之后,再串行执行合并操作(合并阶段并行化的收益很低,还容易引入同步问题,所以没必要)。

实现方式1:用线程池(ExecutorService)

如果你已经有了串行的归并排序代码,这种方式改动最小。我们用线程池来管理并发任务,避免频繁创建销毁线程的开销:

import java.util.Arrays;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

public class ConcurrentMergeSort {
    // 根据CPU核心数创建线程池,平衡并行度和开销
    private static final ExecutorService executor = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());

    public void mergeSort(int[] arr) {
        // 小数组用串行排序,避免线程开销(阈值可以自己调,比如1000)
        if (arr.length <= 1000) {
            Arrays.sort(arr);
            return;
        }
        if (arr.length <= 1) {
            return;
        }
        int mid = arr.length / 2;
        int[] leftSubArray = Arrays.copyOfRange(arr, 0, mid);
        int[] rightSubArray = Arrays.copyOfRange(arr, mid, arr.length);

        // 提交左右排序任务到线程池
        Future<?> leftFuture = executor.submit(() -> mergeSort(leftSubArray));
        Future<?> rightFuture = executor.submit(() -> mergeSort(rightSubArray));

        // 等待两个任务都完成,再执行合并
        try {
            leftFuture.get();
            rightFuture.get();
        } catch (Exception e) {
            Thread.currentThread().interrupt();
            throw new RuntimeException("排序任务执行失败", e);
        }

        merge(arr, leftSubArray, rightSubArray);
    }

    // 你的合并逻辑参考(应该已经实现了)
    private void merge(int[] result, int[] left, int[] right) {
        int i = 0, j = 0, k = 0;
        while (i < left.length && j < right.length) {
            if (left[i] <= right[j]) {
                result[k++] = left[i++];
            } else {
                result[k++] = right[j++];
            }
        }
        while (i < left.length) result[k++] = left[i++];
        while (j < right.length) result[k++] = right[j++];
    }

    // 程序结束前记得关闭线程池,否则JVM不会退出
    public static void shutdownExecutor() {
        executor.shutdown();
        try {
            if (!executor.awaitTermination(60, java.util.concurrent.TimeUnit.SECONDS)) {
                executor.shutdownNow();
            }
        } catch (InterruptedException e) {
            executor.shutdownNow();
            Thread.currentThread().interrupt();
        }
    }
}
实现方式2:用ForkJoinPool(更适合递归并行任务)

Java的ForkJoinPool专门为递归拆分的并行任务设计,它会自动管理线程,避免过度创建线程,性能更优。这也是JDK自带的Arrays.parallelSort的底层实现思路:

import java.util.Arrays;
import java.util.concurrent.RecursiveAction;
import java.util.concurrent.ForkJoinPool;

public class ForkJoinMergeSort extends RecursiveAction {
    private final int[] arr;
    private final int start;
    private final int end;
    // 小数组阈值:当区间小于等于这个值时,用串行排序
    private static final int THRESHOLD = 1000;

    // 对外暴露的入口方法
    public static void sort(int[] arr) {
        ForkJoinPool pool = new ForkJoinPool();
        pool.invoke(new ForkJoinMergeSort(arr));
        pool.shutdown();
    }

    private ForkJoinMergeSort(int[] arr) {
        this(arr, 0, arr.length - 1);
    }

    private ForkJoinMergeSort(int[] arr, int start, int end) {
        this.arr = arr;
        this.start = start;
        this.end = end;
    }

    @Override
    protected void compute() {
        // 小区间用串行排序,避免线程开销
        if (end - start <= THRESHOLD) {
            Arrays.sort(arr, start, end + 1);
            return;
        }
        int mid = (start + end) / 2;
        // 拆分左右任务
        ForkJoinMergeSort leftTask = new ForkJoinMergeSort(arr, start, mid);
        ForkJoinMergeSort rightTask = new ForkJoinMergeSort(arr, mid + 1, end);
        // 并行执行左右任务,invokeAll会自动等待子任务完成
        invokeAll(leftTask, rightTask);
        // 合并结果
        merge(start, mid, end);
    }

    private void merge(int start, int mid, int end) {
        int[] temp = Arrays.copyOfRange(arr, start, end + 1);
        int i = start, j = mid + 1, k = 0;
        while (i <= mid && j <= end) {
            if (arr[i] <= arr[j]) {
                temp[k++] = arr[i++];
            } else {
                temp[k++] = arr[j++];
            }
        }
        while (i <= mid) temp[k++] = arr[i++];
        while (j <= end) temp[k++] = arr[j++];
        // 把临时数组复制回原数组
        System.arraycopy(temp, 0, arr, start, temp.length);
    }
}
关键注意事项
  • 小数组阈值:一定要设置一个阈值,当子数组/区间足够小时,改用串行排序。因为创建线程、调度线程的开销是固定的,对于很小的数组,串行排序的速度反而比并发快。这个阈值可以根据你的测试调整,一般在几百到几千之间。
  • 线程安全:不用担心线程安全问题,因为每个线程处理的都是独立的子数组或者原数组的不同区间,没有共享的可变状态。
  • 资源回收:线程池使用完毕后一定要关闭,否则JVM进程会一直挂着不退出。
  • 异常处理:并发任务中要捕获并处理异常,避免某个线程的异常导致整个排序任务静默失败。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.21 08:11:17