恭喜DK焕发第二春,许秀永远滴神!
在开发过程中,我们常常会遇到需要并行执行多个计算任务的场景,这些任务通常是相互独立的,最终我们需要将它们的结果合并成最终的输出。为了提高计算效率并减少响应时间,使用多线程并行执行这些计算任务是一个有效的方案。今天,我们将介绍如何使用 Java 8 中的 CompletableFuture 来简化异步操作的处理,提升多线程计算的效率,并使用合适的方式聚合这些结果。
为什么选择 CompletableFuture?
CompletableFuture 是 Java 8 引入的一个类,它使得异步编程变得更加简单和直观。相比传统的线程池或 Future,CompletableFuture 提供了更多的功能,如:
• 异步执行任务
• 支持任务链式处理
• 结果合并、异常处理、超时控制等
通过使用 CompletableFuture,我们可以避免回调地狱,同时保持代码简洁易懂。
解决方案
场景描述
假设我们有一个系统,需要并行处理多个独立的计算任务(例如,数据处理、文件读取等),并将它们的结果合并为最终输出。为了提高计算效率,我们需要使用线程池并发执行这些任务,之后将结果聚合。
使用 CompletableFuture 执行并行任务
首先,我们需要定义多个计算任务,并通过 CompletableFuture 异步执行这些任务。然后,我们将所有任务的结果合并为一个列表或其他类型的数据结构。
import java.util.concurrent.*;
import java.util.List;
import java.util.ArrayList;
import java.util.stream.Collectors;
public class AsyncTaskExecutor {
// 创建一个线程池
private static final ExecutorService executorService = Executors.newFixedThreadPool(4);
public static void main(String[] args) throws InterruptedException, ExecutionException {
// 假设我们有多个计算任务
List<Callable<Integer>> tasks = new ArrayList<>();
for (int i = 1; i <= 5; i++) {
final int taskId = i;
tasks.add(() -> {
// 模拟计算任务,返回任务ID的平方
Thread.sleep(1000);
return taskId * taskId;
});
}
// 使用 CompletableFuture 并行执行任务
List<CompletableFuture<Integer>> futures = tasks.stream()
.map(task -> CompletableFuture.supplyAsync(() -> {
try {
return task.call();
} catch (Exception e) {
e.printStackTrace();
}
return 0;
}, executorService))
.collect(Collectors.toList());
// 聚合结果
List<Integer> results = CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]))
.thenApply(v -> futures.stream()
.map(CompletableFuture::join)
.collect(Collectors.toList()))
.get();
// 输出最终结果
Integer finalResult = results.stream().mapToInt(Integer::intValue).sum();
System.out.println("最终结果:" + finalResult);
// 关闭线程池
executorService.shutdown();
}
}
代码解析
-
定义计算任务:每个任务通过 Callable 定义,模拟一个耗时操作(这里我们简单地返回任务 ID 的平方)。
-
线程池配置:我们使用 Executors.newFixedThreadPool() 创建一个固定大小的线程池,最大支持 4 个线程并行执行任务。
-
使用 CompletableFuture 执行任务:通过 CompletableFuture.supplyAsync() 异步执行每个计算任务,将每个任务的结果封装成 CompletableFuture 对象。
-
结果聚合:使用 CompletableFuture.allOf() 等待所有任务完成,然后通过 join() 方法获取每个任务的结果。最后,我们将所有结果合并,计算总和。
-
关闭线程池:使用 shutdown() 方法优雅地关闭线程池,防止资源泄露。
配置自定义线程池
不建议使用默认的线程池,我们可以自己配置一个,注入到容器中
@Configuration
public class ThreadPoolConfig {
Integer corePoolSize = 4;
Integer maximumPoolSize = 20;
Integer keepAliveTime = 120;
int POOL_SIZE = 1000;
@Bean("forecastSkuSummaryThreadPool")
public ThreadPoolExecutor stockThreadPool() {
ThreadFactory threadFactory = new CustomizableThreadFactory("forecast-sku-summary-thread-pool ");
LinkedBlockingQueue<Runnable> synchronousQueue = new LinkedBlockingQueue<>(POOL_SIZE);
ThreadPoolExecutor executor = new ThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTime, TimeUnit.SECONDS, synchronousQueue, threadFactory);
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
return executor;
}
}
在这个配置中,我们定义了一个自定义线程池,并通过 @Bean 注解将其暴露到 Spring 容器中。通过这种方式,我们可以灵活地调整线程池的大小、线程的存活时间等参数,确保系统的性能和稳定性。
使用 CombinerFeature 聚合多线程结果
为了更好地管理多个任务的结果聚合,我们可以封装成一个通用的类 CombinerFeature,专门处理任务的合并逻辑。
import java.util.List;
import java.util.concurrent.*;
public class CombinerFeature<T, R> {
private final ExecutorService executorService;
private final List<Callable<T>> tasks;
public CombinerFeature(ExecutorService executorService, List<Callable<T>> tasks) {
this.executorService = executorService;
this.tasks = tasks;
}
public R aggregate() throws InterruptedException, ExecutionException {
List<Future<T>> futures = executorService.invokeAll(tasks);
// 聚合所有任务的结果
R result = null;
for (Future<T> future : futures) {
T partialResult = future.get();
if (result == null) {
result = (R) partialResult;
} else {
result = (R) combineResults(result, partialResult); // 合并结果
}
}
return result;
}
private R combineResults(R result, T partialResult) {
// 聚合逻辑(根据业务逻辑调整)
return (R) Integer.valueOf((Integer) result + (Integer) partialResult);
}
}
1. aggregate()
-
作用:聚合所有并发任务的结果。
-
参数:无
-
返回值:合并后的结果。
-
异常:
InterruptedException,ExecutionExceptionpublic R aggregate() throws InterruptedException, ExecutionException
2. combineResults(R result, T partialResult)
• 作用:合并两个任务的结果。
• 参数:
• result: 当前已经合并的结果。
• partialResult: 本次计算任务的部分结果。
• 返回值:合并后的结果。根据实际情况,这个方法需要被重写以适应不同的合并逻辑。
private R combineResults(R result, T partialResult)
3. constructor
• 作用:构造 CombinerFeature 实例,初始化任务和执行器。
• 参数:
• executorService: 任务执行的线程池。
• tasks: 需要并行执行的任务列表(Callable 任务)。
public CombinerFeature(ExecutorService executorService, List<Callable<T>> tasks)
常见用法示例
ExecutorService executorService = Executors.newFixedThreadPool(4);
List<Callable<Integer>> tasks = Arrays.asList(
() -> 1,
() -> 2,
() -> 3
);
CombinerFeature<Integer, Integer> combinerFeature = new CombinerFeature<>(executorService, tasks);
Integer result = combinerFeature.aggregate();
System.out.println("合并后的结果:" + result);
需要重写的方法:
- combineResults:该方法默认是合并 Integer 类型的结果。如果你需要合并其他类型的结果,你可能需要重写该方法来实现不同的聚合逻辑。
其他可能扩展的 API(基于你的应用场景):
• getResults() :返回所有任务的结果(不合并)。可用于调试或者查看每个任务的单独结果。
• isCompleted() :检查所有任务是否完成。
• cancelTasks() :取消所有未执行或正在执行的任务。
下面是一些CompletableFuture常用的方法,需要多个任务并行去处理的时候可以用到:
CompletableFuture方法
| 方法 | 作用 | 参数 | 返回值 |
|---|---|---|---|
allOf() | 等待多个任务全部完成 | CompletableFuture<?>... cfs | CompletableFuture<Void> |
anyOf() | 等待多个任务中任意一个完成 | CompletableFuture<?>... cfs | CompletableFuture<Object> |
supplyAsync() | 异步执行一个没有参数的方法 | Supplier<U> supplier | CompletableFuture<U> |
runAsync() | 异步执行一个没有返回值的任务 | Runnable runnable | CompletableFuture<Void> |
thenApply() | 添加回调函数,在任务完成时执行操作并返回结果 | Function<? super T, ? extends U> fn | CompletableFuture<U> |
thenAccept() | 添加回调函数,在任务完成时执行操作但不返回结果 | Consumer<? super T> action | CompletableFuture<Void> |
thenCombine() | 合并两个 CompletableFuture 的结果 | CompletableFuture<? extends U> other, BiFunction<? super T, ? super U, ? extends V> fn | CompletableFuture<V> |
thenAcceptBoth() | 两个任务都完成时执行操作但不返回结果 | CompletableFuture<? extends T> other, BiConsumer<? super T, ? super T> action | CompletableFuture<Void> |
whenComplete() | 任务完成后执行回调,处理结果或异常 | BiConsumer<? super T, ? super Throwable> action | CompletableFuture<T> |
exceptionally() | 处理异常情况并返回默认值 | Function<Throwable, ? extends T> fn | CompletableFuture<T> |
handle() | 处理结果或异常 | BiFunction<? super T, Throwable, ? extends U> fn | CompletableFuture<U> |
join() | 等待任务完成并获取结果,如果异常抛出 CompletionException | 无 | T |
get() | 等待任务完成并返回结果 | long timeout, TimeUnit unit | T |
get(long timeout, TimeUnit unit) | 等待任务完成并返回结果,支持超时 | long timeout, TimeUnit unit | T |
总结
在多线程计算场景中,使用 CompletableFuture 可以简化代码,提升计算效率。结合线程池的使用,我们可以实现高效的并行计算,并通过 CompletableFuture 对计算结果进行聚合和异常处理。CombinerFeature 类封装了任务的聚合逻辑,当然只是我个人业务场景为了方便累加计算所以这样实现了(处于隐私粗略盖了一下),大家个人用的时候按照CompletableFuture业务编排即可。
希望这篇博客对你有所帮助,如果有任何问题,欢迎留言讨论!