JAVA异步任务执行器

import java.util.List;
import java.util.concurrent.*;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * 异步任务执行器
 *
 * @author XiaoXin
 */
public class AsyncTaskExecutor {

    /**
     * 执行给定的任务列表,并等待所有任务完成。
     *
     * @param taskList       任务列表,其中每个元素都会被传递给任务执行器
     * @param taskExecutor   函数接口,定义了如何执行单个任务
     * @param <T>            输入类型
     * @param <R>            输出类型
     * @param threadPoolSize 线程池大小
     * @return 包含所有任务结果的列表
     */
    public static <T, R> List<R> executeTasks(List<T> taskList, Function<T, R> taskExecutor, int threadPoolSize) {
        ExecutorService executorService = Executors.newFixedThreadPool(threadPoolSize);
        try {
            // 将所有任务转换为 CompletableFuture 对象
            List<CompletableFuture<R>> futures = taskList.stream()
                    .map(task -> CompletableFuture.supplyAsync(() -> taskExecutor.apply(task), executorService))
                    .collect(Collectors.toList());

            // 等待所有异步任务完成,并收集结果
            return futures.stream()
                    // 阻塞直到结果准备就绪
                    .map(CompletableFuture::join)
                    .collect(Collectors.toList());
        } finally {
            // 关闭线程池,确保线程池在所有任务完成后关闭
            executorService.shutdown();
        }
    }

    /**
     * 执行给定的任务列表,并等待所有任务完成。默认使用的线程池大小为 CPU 核心数。
     *
     * @param taskList     任务列表,其中每个元素都会被传递给任务执行器
     * @param taskExecutor 函数接口,定义了如何执行单个任务
     * @param <T>          输入类型
     * @param <R>          输出类型
     * @return 包含所有任务结果的列表
     */
    public static <T, R> List<R> executeTasks(List<T> taskList, Function<T, R> taskExecutor) {
        // 使用 CPU 核心数作为线程池的默认大小
        int threadPoolSize = Runtime.getRuntime().availableProcessors();
        return executeTasks(taskList, taskExecutor, threadPoolSize);
    }

    /**
     * 执行给定的任务列表,并等待所有任务完成,返回合并后的任务结果列表。
     * 此方法会把每个线程的结果集合并成一个大列表。
     *
     * @param taskList       任务列表,其中每个元素都会被传递给任务执行器
     * @param taskExecutor   函数接口,定义了如何执行单个任务
     * @param <T>            输入类型
     * @param <R>            输出类型
     * @param threadPoolSize 线程池大小
     * @return 合并后的任务结果列表
     */
    public static <T, R> List<R> executeAndMergeTasks(List<T> taskList, Function<T, List<R>> taskExecutor, int threadPoolSize) {
        ExecutorService executorService = Executors.newFixedThreadPool(threadPoolSize);

        try {
            // 将所有任务转换为 CompletableFuture 对象,并将结果扁平化
            List<CompletableFuture<List<R>>> futures = taskList.stream()
                    .map(task -> CompletableFuture.supplyAsync(
                            () -> taskExecutor.apply(task), executorService))
                    .collect(Collectors.toList());

            // 等待所有异步任务完成,并合并所有的结果
            return futures.stream()
                    // 获取每个任务的结果(List<R>)
                    .map(CompletableFuture::join)
                    // 将每个列表的元素"展开"成一个单一流
                    .flatMap(List::stream)
                    // 收集成一个单一的List<R>
                    .collect(Collectors.toList());
        } finally {
            // 关闭线程池,确保线程池在所有任务完成后关闭
            executorService.shutdown();
        }
    }

    /**
     * 执行给定的任务列表,并等待所有任务完成,返回合并后的任务结果列表,默认使用的线程池大小为 CPU 核心数。。
     * 此方法会把每个线程的结果集合并成一个大列表。
     *
     * @param taskList     任务列表,其中每个元素都会被传递给任务执行器
     * @param taskExecutor 函数接口,定义了如何执行单个任务
     * @param <T>          输入类型
     * @param <R>          输出类型
     * @return 包含所有任务结果的列表
     */
    public static <T, R> List<R> executeAndMergeTasks(List<T> taskList, Function<T, List<R>> taskExecutor) {
        // 使用 CPU 核心数作为线程池的默认大小
        int threadPoolSize = Runtime.getRuntime().availableProcessors();
        return executeAndMergeTasks(taskList, taskExecutor, threadPoolSize);
    }


    /**
     * AllOf
     * 执行给定的任务列表,并等待所有任务完成。
     *
     * @param taskList       任务列表,其中每个元素都会被传递给任务执行器
     * @param taskExecutor   函数接口,定义了如何执行单个任务
     * @param <T>            输入类型
     * @param <R>            输出类型
     * @param threadPoolSize 线程池大小
     * @return 包含所有任务结果的列表
     */
    public static <T, R> List<R> executeAndMergeTasksAllOf(List<T> taskList, Function<T, List<R>> taskExecutor, int threadPoolSize) {
        ExecutorService executorService = Executors.newFixedThreadPool(threadPoolSize);
        try {
            // 将所有任务转换为 CompletableFuture 对象
            List<CompletableFuture<List<R>>> futures = taskList.stream()
                    .map(task -> CompletableFuture.supplyAsync(() -> taskExecutor.apply(task), executorService))
                    .collect(Collectors.toList());

            // 使用 allOf 来确保所有任务都完成,并使用 thenApply 来收集结果
            CompletableFuture<Void> allFutures = CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]));

            // 当所有任务完成后,收集并返回结果
            return allFutures.thenApply(v ->
                    futures.stream()
                            .map(CompletableFuture::join)
                            .flatMap(List::stream)
                            .collect(Collectors.toList())
            ).get(); // 阻塞直到所有任务完成

        } catch (InterruptedException | ExecutionException e) {
            throw new RuntimeException("Error executing tasks", e);
        } finally {
            // 关闭线程池,确保线程池在所有任务完成后关闭
            executorService.shutdown();
        }
    }

    /**
     * 、
     * ThenApply
     * 执行给定的任务列表,并等待所有任务完成,返回合并后的任务结果列表。
     * 此方法会把每个线程的结果集合并成一个大列表。
     *
     * @param taskList       任务列表,其中每个元素都会被传递给任务执行器
     * @param taskExecutor   函数接口,定义了如何执行单个任务
     * @param <T>            输入类型
     * @param <R>            输出类型
     * @param threadPoolSize 线程池大小
     * @return 合并后的任务结果列表
     */
    public static <T, R> List<R> executeAndMergeTasksThenApply(List<T> taskList, Function<T, List<R>> taskExecutor, int threadPoolSize) {
        ExecutorService executorService = Executors.newFixedThreadPool(threadPoolSize);

        try {
            // 将所有任务转换为 CompletableFuture 对象,并将结果扁平化
            List<CompletableFuture<List<R>>> futures = taskList.stream()
                    .map(task -> CompletableFuture.supplyAsync(
                            () -> taskExecutor.apply(task), executorService))
                    .collect(Collectors.toList());

            // 使用 allOf 来确保所有任务都完成,并使用 thenApply 来合并结果
            CompletableFuture<Void> allFutures = CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]));

            // 当所有任务完成后,合并所有的结果
            return allFutures.thenApply(v ->
                    futures.stream()
                            .map(CompletableFuture::join)
                            .flatMap(List::stream)
                            .collect(Collectors.toList())
            ).get(); // 阻塞直到所有任务完成

        } catch (InterruptedException | ExecutionException e) {
            throw new RuntimeException("Error executing and merging tasks", e);
        } finally {
            // 关闭线程池,确保线程池在所有任务完成后关闭
            executorService.shutdown();
        }
    }
}

CompletableFuture.allOf(), CompletableFuture.join(), 和 thenApply()

CompletableFuture.allOf(), CompletableFuture.join(), 和 thenApply() 都是处理异步任务时常用的工具,它们各自适用于不同的场景。让我们根据不同的需求来分析这些方法各自的适用场景。

1. CompletableFuture.allOf()

适用场景:
  • 等待多个异步任务完成,而不关心它们的结果
  • allOf 用于等待多个异步任务完成,且它本身不关心每个任务的返回结果。如果你只关心任务是否完成,或者只需要在所有任务完成后执行某些操作,而不需要获取每个任务的具体结果,使用 allOf 是最合适的。
  • 当多个任务之间没有依赖关系时
  • 如果你有多个异步任务,并且这些任务之间没有依赖关系(即每个任务独立执行),你可以使用 allOf 来等待它们的完成,处理完成后的合并操作(例如通知系统其他部分所有任务已完成)。
示例:
  • 执行多个数据库操作或多个 HTTP 请求,等待它们都完成后执行一些操作,但不需要从每个操作中提取结果。
List<CompletableFuture<Void>> futures = Arrays.asList(
    CompletableFuture.runAsync(() -> doTask1()),
    CompletableFuture.runAsync(() -> doTask2()),
    CompletableFuture.runAsync(() -> doTask3())
);

CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]))
    .thenRun(() -> System.out.println("All tasks completed."));

2. CompletableFuture.join()

适用场景:
  • 等待单个异步任务的完成并获取结果
  • 如果你只需要等待一个异步任务完成并且想获取它的返回结果,使用 join() 是一个直接且简洁的方法。与 get() 不同的是,join() 会抛出 CompletionException 而不是 ExecutionException,因此它的异常处理机制与 get() 略有不同。
  • 任务之间没有依赖关系时,逐个等待结果
  • 适合用于逐个等待异步任务的结果。在你有多个异步任务需要完成时,若你希望按照顺序等待每个任务完成并获取它们的结果,可以使用 join()
示例:
  • 执行多个异步任务,等待每个任务完成并获取其结果。
CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> "Result 1");
CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> "Result 2");
CompletableFuture<String> future3 = CompletableFuture.supplyAsync(() -> "Result 3");

String result1 = future1.join();
String result2 = future2.join();
String result3 = future3.join();

System.out.println(result1);  // 输出: Result 1
System.out.println(result2);  // 输出: Result 2
System.out.println(result3);  // 输出: Result 3
使用场景:
  • 比如你需要执行多个独立的任务,每个任务会返回一个结果,你可以通过 join() 获取每个任务的结果。

3. thenApply()

适用场景:
  • 在任务完成后执行进一步的操作
  • thenApply() 适用于你想在某个 CompletableFuture 完成后立即对其结果进行进一步处理的情况。这通常用于任务的后续操作,如数据转换、计算、合并等。
  • 链式操作
  • 当你需要执行多个依赖顺序的任务时,thenApply() 允许你创建链式操作,确保每个操作在前一个操作完成之后执行。它适用于你希望在任务完成后执行某个操作并返回处理结果的场景。
示例:
  • 处理异步任务的结果,并进行后续的计算或转换。
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> "Result");
CompletableFuture<Integer> lengthFuture = future.thenApply(result -> result.length());

lengthFuture.thenAccept(length -> System.out.println("Length: " + length));  // 输出: Length: 5
使用场景:
  • 比如你异步加载数据并希望基于加载的结果进行进一步处理,比如将数据转换为另一种格式或进行一些计算。

4. 场景总结

方法

适用场景

举例

CompletableFuture.allOf()

1. 等待多个异步任务完成。

2. 不关心每个任务的返回结果。

3. 多个任务之间没有依赖关系。

多个异步的数据库查询或HTTP请求,等待它们都完成后执行某些操作(例如通知其他系统)。

CompletableFuture.join()

1. 等待单个异步任务的完成并获取结果。

2. 多个独立任务的结果,逐个等待并获取。

执行多个异步任务,每个任务返回结果,等待每个任务完成并处理它们的结果。

thenApply()

1. 在任务完成后执行处理,进行转换或后续操作。

2. 链式操作任务依赖顺序。

处理异步任务的结果(如数据转换、计算)后返回一个新的结果。链式调用以处理异步任务的结果。

5. 选择合适的方法

  • 当你需要等待多个任务完成时,并且不关心它们的结果,可以使用 allOf
  • 当你需要等待单个任务并获取其结果时,可以使用 join()
  • 当你需要基于异步任务的结果进行后续操作时,并且希望执行链式操作,使用 thenApply() 是最合适的选择。