CompletableFuture用法详解
1. 前言
1.1 Java支持的多线程开启方式
根据Oracle官方出具的Java文档说明,创建线程的方式只有两种:继承Thread或者实现Runnable接口。但是这两种方法都存在一个缺陷,没有返回值,也就是说我们无法得知线程执行结果。虽然简单场景下已经满足,但是当我们需要返回值的时候怎么办呢? Java 1.5 以后的Callable和Future接口就解决了这个问题,我们可以通过向线程池提交一个Callable来获取一个包含返回值的Future对象,从此,我们的程序逻辑就不再是同步顺序。
1.2 Future接口的局限性
在Java多线程体系中,Future接口(位于java.util.concurrent包)是早期用于处理异步任务结果的重要工具,它提供了获取异步计算结果、取消任务等能力。但随着并发场景的复杂化,Future的局限性逐渐显现,主要体现在以下几个方面:
1.获取结果的方法会阻塞,缺乏非阻塞回调机制
Future的get()方法是获取异步结果的核心方式,但它存在严重的阻塞问题:
- 如果任务尚未完成,调用get()会导致当前线程阻塞,直到任务完成或超时(get(long timeout, TimeUnit unit))。
- 无法注册“回调函数”,让任务完成后自动触发后续操作。必须主动调用get()检查结果,这在需要“任务完成后立即处理结果”的场景中非常不便。
举例:若要实现“任务A完成后自动执行任务B”,使用Future需要手动轮询isDone()判断任务A是否完成,再触发任务B,代码冗余且效率低。
2.无法高效组合多个异步任务
在实际场景中,常需要组合多个异步任务的结果(如“等待所有任务完成”或“只要有一个任务完成”),但Future缺乏原生支持:
- 若要等待多个Future完成,需手动循环调用get(),且无法感知“所有任务都完成”的时机(需自行维护计数)。
- 若要实现“任一任务完成就处理结果”,Future没有对应的API,必须通过额外线程轮询,效率低下。
对比:Java 8引入的CompletableFuture提供了allOf()(等待所有完成)、anyOf()(任一完成)等方法,直接解决了这一问题。
3.任务取消能力有限且不灵活
Future的cancel(boolean mayInterruptIfRunning)方法用于取消任务,但存在明显局限:
- 若任务已开始执行(mayInterruptIfRunning为true),仅能通过中断(interrupt())尝试停止任务,但无法强制终止(尤其对于不响应中断的任务,如未检查Thread.interrupted()的循环)。
- 若任务尚未开始(处于线程池队列中),cancel()可成功移除任务;但一旦任务运行中,取消效果完全依赖任务自身的中断处理逻辑。
4.异常处理机制简陋
异步任务执行过程中若抛出异常,Future的处理方式非常被动:
- 异常不会主动抛出,而是封装在ExecutionException中,只有调用get()时才会被触发。
- 无法在任务执行过程中捕获异常并处理,必须等到调用get()时才能知晓,不利于及时容错。
举例:若任务因网络异常失败,Future不会即时通知,直到调用get()才会抛出异常,可能导致错误处理延迟。
5.无法获取任务执行进度
Future仅提供isDone()判断任务是否完成,没有任何API获取任务的执行进度(如“完成了50%”)。
对于长时间运行的任务(如下载大文件、复杂计算),无法实时反馈进度,用户体验差。
6.扩展性差,不支持链式操作
Future的接口设计简单,仅包含get()、cancel()、isDone()等基础方法,不支持任务的链式编排。
在复杂业务场景中(如“任务A→任务B→任务C”的依赖关系),需要手动嵌套Future,代码可读性和可维护性极差。
总结
Future是Java早期异步编程的基础工具,但其设计过于简单,无法满足复杂并发场景的需求(如回调、组合、进度跟踪等)。正因为这些局限性,Java 8引入了CompletableFuture,它实现了Future接口并扩展了丰富的异步编程能力(回调、链式操作、组合任务等),成为现代Java异步编程的首选。
简单来说:Future适合简单的“提交任务→获取结果”场景,而复杂的异步流程(尤其是需要多任务协作、回调触发的场景)需使用CompletableFuture。
1.3 神奇的CompletableFuture
CompletableFuture是 Java 8 引入的一个异步编程工具类(位于java.util.concurrent包),它实现了Future和CompletionStage接口,旨在解决传统Future接口的局限性,提供更强大、更灵活的异步编程能力。
1. 核心定位
CompletableFuture不仅可以像Future一样表示一个异步计算的结果,还支持通过回调机制自动处理结果、组合多个异步任务、异常处理等复杂操作,无需手动轮询或阻塞等待,大幅简化了异步代码的编写。
2. 主要特点(对比Future的优势)
1.非阻塞的回调机制,支持链式操作
CompletableFuture提供了一系列以then开头的方法(如thenApply、thenAccept、thenRun等),允许在异步任务完成后自动触发后续操作,无需主动调用get()阻塞等待。
这些方法支持链式编排,形成“任务流水线”,代码可读性更高。
示例:
// 异步计算一个数字的平方,然后加1,最后打印结果
CompletableFuture<Integer> future = CompletableFuture
.supplyAsync(() -> 3 * 3) // 异步任务:计算9
.thenApply(result -> result + 1) // 任务完成后自动执行:9+1=10
.thenAccept(result -> System.out.println("结果:" + result)); // 最终处理:打印10
2.支持多个异步任务的组合
CompletableFuture提供了原生方法组合多个异步任务,解决了Future无法高效组合任务的问题:
- allOf(CompletableFuture<?>... cfs):等待 所有任务完成 后再执行后续操作(适合“且”逻辑)。
- anyOf(CompletableFuture<?>... cfs):任一任务完成 后就执行后续操作(适合“或”逻辑)。
示例:
// 三个异步任务
CompletableFuture<String> task1 = CompletableFuture.supplyAsync(() -> "任务1完成");
CompletableFuture<String> task2 = CompletableFuture.supplyAsync(() -> "任务2完成");
CompletableFuture<String> task3 = CompletableFuture.supplyAsync(() -> "任务3完成");
// 等待所有任务完成后打印"全部完成"
CompletableFuture.allOf(task1, task2, task3)
.thenRun(() -> System.out.println("全部任务完成"));
3.灵活的异常处理
CompletableFuture提供了专门的异常处理方法,无需等到调用get()时才捕获异常,可在任务执行过程中即时处理:
- exceptionally(Function<Throwable, T> fn):任务抛出异常时,返回一个默认值或处理结果。
- handle(BiFunction<T, Throwable, U> fn):无论任务成功或失败,都执行后续处理(同时接收结果和异常)。
示例:
CompletableFuture<Integer> future = CompletableFuture
.supplyAsync(() -> {
if (true) {
throw new RuntimeException("计算失败"); // 模拟异常
}
return 100;
})
.exceptionally(ex -> { // 捕获异常,返回默认值
System.out.println("发生异常:" + ex.getMessage());
return 0; // 异常时返回0
})
.thenAccept(result -> System.out.println("最终结果:" + result)); // 输出:最终结果:0
4.无需依赖显式线程管理
CompletableFuture的异步方法(如supplyAsync、runAsync)默认使用ForkJoinPool.commonPool()作为线程池,无需手动创建线程或线程池;也可显式传入自定义线程池,灵活控制资源。
示例:
// 使用自定义线程池执行异步任务
ExecutorService executor = Executors.newFixedThreadPool(3);
CompletableFuture<String> future = CompletableFuture.supplyAsync(
() -> {
return "使用自定义线程池";
},
executor
); // 传入自定义线程池
5.可主动完成任务(手动触发结果)
CompletableFuture允许通过complete(T value)或completeExceptionally(Throwable ex)方法手动设置结果或异常,无需等待异步任务执行完成。这在测试或需要外部触发的场景中非常有用。
示例:
CompletableFuture<String> future = new CompletableFuture<>();
// 另一个线程手动完成任务
new Thread(() -> {
try {
Thread.sleep(1000);
future.complete("手动设置的结果"); // 主动设置成功结果
} catch (InterruptedException e) {
future.completeExceptionally(e); // 主动设置异常
}
}).start();
System.out.println(future.get()); // 输出:手动设置的结果
3. 总结
CompletableFuture是 Java 异步编程的“瑞士军刀”,其核心特点是:非阻塞回调、链式操作、任务组合、灵活异常处理,完美解决了传统Future的阻塞、无法组合、异常处理简陋等问题。它广泛应用于需要高效处理异步任务的场景(如分布式服务调用、IO操作、并行计算等),是现代Java并发编程的核心工具之一。
2. CompletableFuture详细使用
2.1 定义线程池
虽然CompletableFuture 的异步方法(如 supplyAsync、runAsync)默认使用 ForkJoinPool.commonPool() 作为线程池,无需手动创建线程或线程池。
但是使用ForkJoinPool创建线程会产生许多问题,在实际项目中一般都会创建池,CompletableFuture执行时由线程池进行托管。
1. 使用ForkJoinPool.commonPool()创建线程的问题
在使用CompletableFuture时,若依赖默认的ForkJoinPool.commonPool()(公共线程池)创建线程,可能会产生以下问题:
- 线程资源竞争与耗尽
- ForkJoinPool.commonPool()是整个应用共享的线程池,不仅被CompletableFuture使用,还会被Stream.parallel()、Fork/Join任务等其他组件共享。若大量异步任务(尤其是长耗时任务)提交到公共池,会导致线程资源被争抢,甚至耗尽,进而阻塞其他依赖公共池的任务(如并行流处理),影响整个应用的并发能力。
- 线程数量固定,不适应IO密集型任务
- 公共池的默认线程数为 Runtime.getRuntime().availableProcessors() - 1(如8核CPU默认7个线程),这个设置更适合CPU密集型任务(计算为主,线程利用率高)。但对于IO密集型任务(如网络请求、文件读写,线程常处于阻塞状态),固定的线程数会成为瓶颈——线程阻塞时无法处理新任务,导致任务排队延迟,降低吞吐量。
- 守护线程特性导致任务可能被意外中断
- ForkJoinPool.commonPool()的线程是守护线程(daemon thread)。当JVM中所有非守护线程(如主线程)结束时,即使公共池中有未完成的任务,JVM也会直接退出,导致任务被强制中断,可能引发数据不一致或资源未释放等问题。
- 异常处理与调试困难
- 公共池的线程命名格式固定(如ForkJoinPool.commonPool-worker-1),且无法自定义线程名称、优先级等属性。当任务抛出异常或出现问题时,难以通过线程名定位具体业务场景;此外,公共池的异常堆栈可能被简化,增加问题排查难度。
- 缺乏任务隔离能力
- 公共池中的任务没有隔离机制,若某个任务出现死锁、无限循环等问题,会占用公共池的线程资源,直接影响其他不相关的任务(如其他业务模块的异步操作),导致“一损俱损”的连锁反应。
ForkJoinPool.commonPool()的设计更适合轻量、短时的并行计算任务,而非复杂的业务异步场景。在使用CompletableFuture时,建议根据业务需求自定义线程池(如ThreadPoolExecutor),通过控制线程数、设置非守护线程、自定义命名等方式,避免上述问题,提升异步任务的稳定性和可维护性。
2. 线程池配置
在application.yml中添加如下配置:
spring:
application:
name: others
task:
execution:
thread-name-prefix: thread_pool_name_
pool:
core-size: 10
max-size: 20
queue-capacity: 10000
3. 线程池Configuration
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler;
import org.springframework.aop.interceptor.SimpleAsyncUncaughtExceptionHandler;
import org.springframework.boot.autoconfigure.task.TaskExecutionProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.AsyncConfigurer;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import java.util.concurrent.Executor;
/**
* 线程池配置类定义
* @author gary.wang
*
*/
@Configuration
@EnableAsync
public class AsyncConfiguration implements AsyncConfigurer {
private final Logger log = LoggerFactory.getLogger(AsyncConfiguration.class);
private final TaskExecutionProperties taskExecutionProperties;
public AsyncConfiguration(TaskExecutionProperties taskExecutionProperties) {
this.taskExecutionProperties = taskExecutionProperties;
}
@Override
@Bean(name = "taskExecutor")
public Executor getAsyncExecutor() {
log.info("创建异步执行线程池开始");
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(taskExecutionProperties.getPool().getCoreSize());
executor.setMaxPoolSize(taskExecutionProperties.getPool().getMaxSize());
executor.setQueueCapacity(taskExecutionProperties.getPool().getQueueCapacity());
executor.setThreadNamePrefix(taskExecutionProperties.getThreadNamePrefix());
log.info("创建异步执行线程池结束");
return executor;
}
@Override
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
return new SimpleAsyncUncaughtExceptionHandler();
}
}
2.2 实例化CompletableFuture
实例化方法:
public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier);
public static <U> CompletableFuture<U> supplyAsync(
Supplier<U> supplier,
Executor executor
);
public static CompletableFuture<Void> runAsync(Runnable runnable);
public static CompletableFuture<Void> runAsync(
Runnable runnable,
Executor executor
);
有两种格式:
- supplyAsync:返回结果CompletableFuture<U>,这种方法可以返回异步线程执行后的结果;
- run:返回结果CompletableFuture<Void>,这种方法不会返回结果,单单是执行线程任务。
- supplyAsync、run:每个方法都有一个传入线程池配置的重载方法,用来传入系统中已有的线程池定义,实际项目应用时,应该都使用带有自定义线程池参数的方法。
示例:
@Test
void init() {
CompletableFuture<String> future = CompletableFuture.supplyAsync(
() -> {
log.info("supplyAsync 线程: {}", Thread.currentThread().getName());
return "hello world";
},
taskExecutor
);
log.info("future result: {}", future.join());
}
2.3 获取结果
public T get()
public T get(long timeout, TimeUnit unit)
public T getNow(T valueIfAbsent)
public T join()
1:get():get()方法会阻塞线程直到任务完成。
如果使用下面的代码创建一个CompletableFuture,并执行get方法将会一直阻塞下去,不会看到log.info打印的输出。
/**
* get方法阻塞线程知道任务完成测试
* 主线程会一直阻塞,因为这种方式创建的CompletableFuture从未完成,因此不会执行到log.info输出任何内容
* @throws ExecutionException
* @throws InterruptedException
*/
@Test
void get() throws InterruptedException, ExecutionException {
CompletableFuture<Integer> future = new CompletableFuture<>();
Integer integer = future.get();
log.info("get 方法阻塞线程知道任务完成测试, 结果: {}", integer);//不会看到此句日志打印
}
2:get(long timeout, TimeUnit unit):同样会阻塞线程直到任务完成或者超过指定的超时时间。
/**
* get方法超时阻塞线程直到任务完成或者超过指定的超时时间 测试
*/
@Test
void getTimeout() {
CompletableFuture<Integer> future = new CompletableFuture<>();
try {
Integer integer = future.get(2, TimeUnit.SECONDS);
log.info("getTimeout 方法阻塞线程知道任务完成测试, 结果: {}", integer);
} catch (InterruptedException | ExecutionException | TimeoutException e) {
log.error(
"getTimeout 方法阻塞线程直到任务完成测试, 异常: {}",
e.getMessage()
);
}
}
3:getNow(T valueIfAbsent):参数valueIfAbsent的意思是当计算结果不存在或者Now时刻没有完成任务,则返回一个默认值。
4:join():join与get的区别在于join返回计算结果或者一个unchecked异常(不需要明确的try-catch捕获),而get()返回一个具体的异常(InterruptedException, ExecutionException)。
2.4 计算完成后续操作
1. complete
特点:接收上一步的结果和异常,可内部处理异常做兜底操作,无返回结果。
public CompletableFuture<T> whenComplete(BiConsumer<? super T,? super Throwable> action)
public CompletableFuture<T> whenCompleteAsync(BiConsumer<? super T,? super Throwable> action)
public CompletableFuture<T> whenCompleteAsync(BiConsumer<? super T,? super Throwable> action, Executor executor)
- 方法1和方法2之间的区别在于是否是用异步处理。
- 方法2和方法3之间的区别在于是否是用自定义的线程池。
- 前三个方法入参都是BiConsumer,即前三个方法对结果或异常进行 “消费”(如日志记录、简单处理),但不改变原任务的结果(即使在方法中修改result的值也无效)。
- 由于complete方法的消费特性,complete方法时候代码执行到最后针对返回结果的日志记录等收尾动作,不会改变结果。
whenCompleteAsync使用示例
/**
* 4. 计算完成后操作-complete
* whenCompleteAsync方法测试
*/
@Test
void whenCompleteAsync() {
CompletableFuture<String> future = CompletableFuture
.supplyAsync(
() -> {
log.info("supplyAsync 线程: {}", Thread.currentThread().getName());
return "hello world";
},
taskExecutor
)
.whenCompleteAsync(
(result, throwable) -> {
log.info(
"whenCompleteAsync 线程: {}",
Thread.currentThread().getName()
);
if (throwable != null) {
log.error(
"whenCompleteAsync 方法异常测试, 异常: {}",
throwable.getMessage()
);
} else {
log.info("whenCompleteAsync 方法测试, 入参: {}", result);
}
},
taskExecutor
);
log.info("whenCompleteAsync 方法测试, 结果: {}", future.join());
}
输出,可以看到supplyAsync和whenCompleteAsync方法对应的是不同的线程。
INFO 28576 --- [r_thread_pool_1] c.s.others.CompletableFutureComposeTest : supplyAsync 线程: other_thread_pool_1
INFO 28576 --- [r_thread_pool_2] c.s.others.CompletableFutureComposeTest : whenCompleteAsync 线程: other_thread_pool_2
INFO 28576 --- [r_thread_pool_2] c.s.others.CompletableFutureComposeTest : whenCompleteAsync 方法测试, 入参: hello world
INFO 28576 --- [ main] c.s.others.CompletableFutureComposeTest : whenCompleteAsync 方法测试, 结果: hello world
2. handle
handle开头的方法和上面complete方法入参没有区别,都接收一个返回结果和一个可抛出异常,区别就在于handle相关的方法具有返回值。
特点:接收上一步的结果和异常,可内部处理异常做兜底操作,有返回结果。
public <U> CompletableFuture<U> handle(BiFunction<? super T,Throwable,? extends U> fn)
public <U> CompletableFuture<U> handleAsync(BiFunction<? super T,Throwable,? extends U> fn)
public <U> CompletableFuture<U> handleAsync(BiFunction<? super T,Throwable,? extends U> fn, Executor executor)
handle开头的方法和complete方法由于入参都接收一个可抛出的异常,因此可以在代码中捕获此异常做兜底操作。
handle使用示例:
/**
* 5. 计算完成后操作-handle
*/
@Test
void handle() {
CompletableFuture<String> future = CompletableFuture
.supplyAsync(
() -> {
log.info("supplyAsync 线程: {}", Thread.currentThread().getName());
return "hello world";
},
taskExecutor
)
.handleAsync(
(result, throwable) -> {
log.info("handleAsync 线程: {}", Thread.currentThread().getName());
if (throwable != null) {
log.error(
"handleAsync 方法异常测试, 异常: {}",
throwable.getMessage()
);
return "handleAsync 方法异常测试, 异常: " + throwable.getMessage();
} else {
log.info("handleAsync 方法测试, 入参: {}", result);
return result + "!";
}
},
taskExecutor
);
log.info("handleAsync 方法测试, 结果: {}", future.join());
}
3. apply
特点:接收上一步的结果,执行时如果发生异常直接向上抛出,有返回结果。
public <U> CompletableFuture<U> thenApply(Function<? super T,? extends U> fn)
public <U> CompletableFuture<U> thenApplyAsync(Function<? super T,? extends U> fn)
public <U> CompletableFuture<U> thenApplyAsync(Function<? super T,? extends U> fn, Executor executor)
- apply相关方法可以返回结果。
- 相比于handle,handle会接收异常,可以让用户自行内部处理,做兜底操作,而apply直接向上抛出异常。
- 如果不想整个链路一直都在处理异常那么可以使用apply方法。
4. accept
特点:只消费,无返回
public CompletableFuture<Void> thenAccept(Consumer<? super T> action)
public CompletableFuture<Void> thenAcceptAsync(Consumer<? super T> action)
public CompletableFuture<Void> thenAcceptAsync(Consumer<? super T> action, Executor executor)
accept使用示例:
@Test
void accept() {
CompletableFuture<Void> future = CompletableFuture
.supplyAsync(
() -> {
log.info("supplyAsync 线程: {}", Thread.currentThread().getName());
return "hello world";
},
taskExecutor
)
.thenAcceptAsync(
result -> {
log.info("acceptAsync 线程: {}", Thread.currentThread().getName());
log.info("acceptAsync 方法测试, 入参: {}", result);
},
taskExecutor
);
log.info("acceptAsync 方法测试, 结果: {}", future.join());
}
打印结果:
\[r_thread_pool_1\] c.s.others.CompletableFutureComposeTest : supplyAsync 线程: other_thread_pool_1
\[r_thread_pool_2\] c.s.others.CompletableFutureComposeTest : acceptAsync 线程: other_thread_pool_2
\[r_thread_pool_2\] c.s.others.CompletableFutureComposeTest : acceptAsync 方法测试, 入参: hello world
\[ main\] c.s.others.CompletableFutureComposeTest : acceptAsync 方法测试, 结果: null
5. exceptionally
小贴士:向线程池提交任务的时候发生的异常属于外部异常,是无法进行捕捉的。exceptionally无法捕捉RejectedExecutionException()
exceptionally可以帮我们捕捉到所有中间过程的异常。方法会给我们一个异常作为参数,我们可以处理这个异常,同时返回一个默认值,相当于是返回兜底值,默认值的类型和上一个操作的返回值相同。
exceptionally使用示例:
@Test
void exceptionally() {
CompletableFuture<BigDecimal> future = CompletableFuture
.supplyAsync(
() -> {
log.info("supplyAsync 线程: {}", Thread.currentThread().getName());
return BigDecimal.ZERO;
},
taskExecutor
)
.thenApplyAsync(
result -> {
log.info("thenApplyAsync 线程: {}", Thread.currentThread().getName());
return BigDecimal.valueOf(1).divide(result, RoundingMode.HALF_UP);
},
taskExecutor
)
.thenApplyAsync(
result -> {
log.info("thenApplyAsync 线程: {}", Thread.currentThread().getName());
log.info("不会执行到这里,因为上一步就报错了。");
return result;
},
taskExecutor
)
.exceptionally(error -> {
log.error("exceptionally 方法异常测试, 异常: {}", error.getMessage());
return BigDecimal.ZERO;
});
log.info("exceptionally 方法测试, 结果: {}", future.join());
}
日志输出:
\[r_thread_pool_1\] c.s.others.CompletableFutureComposeTest : supplyAsync 线程: other_thread_pool_1
\[r_thread_pool_2\] c.s.others.CompletableFutureComposeTest : thenApplyAsync 线程: other_thread_pool_2
\[r_thread_pool_2\] c.s.others.CompletableFutureComposeTest : exceptionally 方法异常测试, 异常: java.lang.ArithmeticException: / by zero
\[ main\] c.s.others.CompletableFutureComposeTest : exceptionally 方法测试, 结果: 0
6. 方法辨析
| 方法 | 触发时机 | 输入 | 输出 | 是否改变结果 | 典型用途 |
| complete(value) | 主动调用 | value(外部提供) | boolean(是否成功设置结果) | ✅(设置最终结果,若未完成) | 手动结束任务,常用于超时兜底、外部条件满足时提前返回 |
| thenApply(fn) | 正常完成时 | 前一步结果 | 转换后的新结果 | ✅(结果被替换) | 对结果进行转换/加工,如字符串转对象 |
| thenAccept(consumer) | 正常完成时 | 前一步结果 | void | ❌(不影响结果) | 消费结果(打印、保存数据库),不需要返回值 |
| handle((res, ex)) | 正常/异常完成时 | 结果或异常 | 新结果 | ✅(结果可被替换) | 统一收尾处理,无论成功/失败都能拿到,适合做异常修复 |
| exceptionally(ex -> …) | 异常完成时 | 异常对象 | 替代结果 | ✅(仅异常时替换结果) | 异常兜底,仅在失败时提供默认值或降级方案 |
2.5 组合式异步编程
2.5.1. thenApply(单任务结果转换)
假设有一个场景,需要查询一名学生每天的课程情况,需要首先需要根据学号查询学生详细信息,然后根据此详细信息查询学生所上的课程。
/**
* 8. thenApply使用示例
*/
@Test
void thenApply() {
String studentNo = "S001";
CompletableFuture<List<Map<String, Object>>> future = CompletableFuture
.supplyAsync(
() -> {
log.info("supplyAsync 线程: {}", Thread.currentThread().getName());
Map<String, Object> studentMap = new HashMap<>();
studentMap.put("studentNo", studentNo);
return studentMap;
},
taskExecutor
)
.thenApplyAsync(
studentMap -> {
log.info("thenApplyAsync 线程: {}", Thread.currentThread().getName());
//模拟根据学生信息查询学生相关的课程信息
List<Map<String, Object>> lessonMapList = new ArrayList<>();
Map<String, Object> lessonMap = new HashMap<>();
lessonMap.put("lessonNo", "L001");
lessonMap.put("lessonName", "Java 基础");
lessonMapList.add(lessonMap);
return lessonMapList;
},
taskExecutor
);
log.info("thenApply 方法测试, 结果: {}", future.join());
}
2.5.2. thenCompose(链式展开新任务)
thenApply和thenCompose看起来很像,都是拿上一步的结果做点事,然后返回一个新的CompletableFuture。但他们的核心区别如下:
| 方法 | 输入 | 输出 | 返回类型 | 主要用途 | 类比 |
| thenApply(fn) | 上一步结果T | 转换结果U | CompletableFuture<U> | 普通转换:把结果加工成一个新值 | map(值映射) |
| thenCompose(fn) | 上一步结果T | 一个CompletableFuture<U> | 直接展开成CompletableFuture<U> | 任务扁平化:避免CompletableFuture<CompletableFuture<U>> | flatMap(展平映射) |
二者对比示例:
/**
* 9. thenApply和thenCompose的区别
*/
@Test
void thenApplyAndThenCompose() {
CompletableFuture<String> cf = CompletableFuture.supplyAsync(() -> "hello");
// thenApply:返回 CompletableFuture<CompletableFuture<Integer>>
CompletableFuture<CompletableFuture<Integer>> r1 = cf.thenApply(s ->
CompletableFuture.supplyAsync(s::length)
);
log.info("thenApply 方法测试, 结果: {}", r1.join().join());
// thenCompose:返回 CompletableFuture<Integer>
CompletableFuture<Integer> r2 = cf.thenCompose(s ->
CompletableFuture.supplyAsync(s::length)
);
log.info("thenCompose 方法测试, 结果: {}", r2.join());
}
输出结果:
c.s.others.CompletableFutureComposeTest : thenApply 方法测试, 结果: 5
c.s.others.CompletableFutureComposeTest : thenCompose 方法测试, 结果: 5
- thenApply:不会帮你“拆开”里面的 CompletableFuture,所以结果是嵌套的。
- thenCompose:自动帮你把里面的 CompletableFuture 展开,直接得到“最终结果”。
- 如果Lambda语句返回的是普通值,就用thenApply,如果返回的是一个CompletableFuture,那么就用thenCompose来避免嵌套。
2.5.3. thenCombine(两个独立任务合并)
<U,V> CompletableFuture<V> thenCombine(
CompletionStage<? extends U> other,
BiFunction<? super T,? super U,? extends V> fn)
- 参数
- other:另一个 CompletableFuture
- fn:合并函数,接收两个任务的结果(this 的结果 T,other 的结果 U)
- 返回:新的 CompletableFuture<V>,包含合并后的结果
thenApply、thenCompose、thenCombine对比
| 方法 | 任务个数 | 输入 | 输出 | 适用场景 |
| thenApply | 1 个 | 上一步结果T | 普通结果U | 单任务结果转换 |
| thenCompose | 1 个 | 上一步结果T | CompletableFuture<U>(被展开) | 链式依赖(结果触发下个异步任务) |
| thenCombine | 2 个 | this 结果T+ other 结果U | 普通结果V | 多任务并行后合并结果 |
thenCombine使用示例:
/**
* 10. thenCombine:组合两个CompletableFuture的结果
*/
@Test
void thenCombine() {
CompletableFuture<String> cf1 = CompletableFuture.supplyAsync(() -> "hello");
CompletableFuture<Integer> cf2 = CompletableFuture.supplyAsync(() -> 123);
CompletableFuture<String> r = cf1.thenCombine(
cf2,
(result1, result2) -> result1 + " -> " + result2
);
log.info("thenCombine 方法测试, 结果: {}", r.join());
}
输出结果:
thenCombine 方法测试, 结果: hello -> 123
2.6 获取所有完成结果-allOf
allOf方法,当所有给定的任务完成后,返回一个全新的已完成CompletableFuture。
使用示例:
/**
* 11. allOf:等待所有CompletableFuture完成
*/
@Test
void allOf() {
List<CompletableFuture<Integer>> cfList = new ArrayList<>();
for (int i = 1; i <= 10; i++) {
int finalI = i;
CompletableFuture<Integer> cf = CompletableFuture.supplyAsync(
() -> {
log.info("supplyAsync 线程: {}", Thread.currentThread().getName());
return finalI * finalI;
},
taskExecutor
);
cfList.add(cf);
}
CompletableFuture<Void> allFuture = CompletableFuture.allOf(
cfList.toArray(new CompletableFuture[0])
);
Integer result = 0;
log.info("allOf 方法测试, 等待全部线程完成,结果: {}", allFuture.join());
for (CompletableFuture<Integer> cf : cfList) {
result += cf.join();
}
log.info("allOf 方法测试, 结果: {}", result);
}
输出结果:
supplyAsync 线程: other_thread_pool_3
supplyAsync 线程: other_thread_pool_1
supplyAsync 线程: other_thread_pool_4
supplyAsync 线程: other_thread_pool_2
supplyAsync 线程: other_thread_pool_5
supplyAsync 线程: other_thread_pool_6
supplyAsync 线程: other_thread_pool_7
supplyAsync 线程: other_thread_pool_8
supplyAsync 线程: other_thread_pool_9
supplyAsync 线程: other_thread_pool_10
allOf 方法测试, 等待全部线程完成,结果: null
allOf 方法测试, 结果: 385
如果遇到需要通过多线程并行处理多个独立的耗时子任务,再汇总结果以提升整体执行效率的情景可以利用allOf函数。
当需要处理一批相互独立、逻辑相似但执行耗时的子任务(如批量数据计算、多源数据查询、并行接口调用等)时,为避免串行执行导致的总时间过长,可采用 “并行化处理 + 统一等待 + 结果汇总” 的方案:
- 任务拆分与并行执行:将整体任务拆分为多个子任务(示例中是计算 1~10 的平方,实际场景可能是复杂的业务计算、IO 操作等),通过CompletableFuture.supplyAsync提交到线程池(taskExecutor),让所有子任务在多线程中并行执行,充分利用 CPU 多核资源。
- 等待所有子任务完成:使用CompletableFuture.allOf创建一个 “聚合 Future”(allFuture),它会在所有子任务对应的CompletableFuture都完成后才会完成,实现 “等待所有并行任务结束” 的同步逻辑。
- 结果汇总:当所有子任务完成后,遍历每个子任务的CompletableFuture,通过join()获取各自的结果(示例中是每个数字的平方),再进行汇总处理(示例中是累加得到总和)。
2.7 获取率先完成的任务结果-anyOf
仅等待Future集合中最快结束的任务完成,比如两个任务试图通过不同的方式计算同一个值,并返回结果。
那个任务执行过程中先遇到了异常也会先返回异常,因此可以使用exceptionally进行兜底。
anyOf使用示例:
/**
* 12. anyOf:等待任意一个CompletableFuture完成
*/
@Test
void anyOf() {
CompletableFuture<String> cf1 = CompletableFuture
.supplyAsync(
() -> {
long sleepTime = RandomUtil.randomLong(1000, 2000);
log.info(
"supplyAsync1 线程: {},本次睡眠时间为:{}",
Thread.currentThread().getName(),
sleepTime
);
try {
Thread.sleep(sleepTime); //随机睡眠一段时间
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
return "hello world";
},
taskExecutor
)
.exceptionally(error -> "supplyAsync1 异常: " + error.getMessage());
CompletableFuture<String> cf2 = CompletableFuture
.supplyAsync(
() -> {
long sleepTime = RandomUtil.randomLong(1000, 2000);
log.info(
"supplyAsync2 线程: {},本次睡眠时间为:{}",
Thread.currentThread().getName(),
sleepTime
);
try {
Thread.sleep(sleepTime); //随机睡眠一段时间
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
return "hello everyone";
},
taskExecutor
)
.exceptionally(error -> "supplyAsync2 异常: " + error.getMessage());
CompletableFuture<Object> r = CompletableFuture
.anyOf(cf1, cf2)
.exceptionally(error -> "anyOf 异常: " + error.getMessage());
log.info("anyOf 方法测试, 结果: {}", r.join());
}
输出结果:
supplyAsync1 线程: other_thread_pool_1,本次睡眠时间为:1575
supplyAsync2 线程: other_thread_pool_2,本次睡眠时间为:1792
anyOf 方法测试, 结果: hello world
3. 几个小例子
3.1. 多个方法组合使用
@Test
void shopping() {
StopWatch stopWatch = new StopWatch();
String userId = "u001"; //用户ID
String goodId = "g001"; //商品ID
//可并行:查询用户信息
stopWatch.start("查询用户信息");
CompletableFuture<Map<String, Object>> userInfoFuture = CompletableFuture
.supplyAsync(
() -> {
log.info("查询用户信息 线程: {}", Thread.currentThread().getName());
Map<String, Object> userMap = new HashMap<>();
userMap.put("userId", userId);
userMap.put("userName", "张三");
return userMap;
},
taskExecutor
)
.exceptionally(error -> {
log.error("查询用户信息异常: {}", error.getMessage());
throw new RuntimeException("查询用户信息异常: " + error.getMessage());
});
stopWatch.stop();
//可并行:查询库存信息
stopWatch.start("查询库存信息");
CompletableFuture<Map<String, Object>> stockInfoFuture = CompletableFuture
.supplyAsync(
() -> {
log.info("查询库存信息 线程: {}", Thread.currentThread().getName());
Map<String, Object> stockMap = new HashMap<>();
stockMap.put("goodId", goodId);
stockMap.put("stock", 100);
return stockMap;
},
taskExecutor
)
.exceptionally(error -> {
log.error("查询库存信息异常: {}", error.getMessage());
throw new RuntimeException("查询库存信息异常: " + error.getMessage());
});
stopWatch.stop();
//可并行:查询商品信息
stopWatch.start("查询商品信息");
CompletableFuture<Map<String, Object>> goodInfoFuture = CompletableFuture
.supplyAsync(
() -> {
log.info("查询商品信息 线程: {}", Thread.currentThread().getName());
Map<String, Object> goodMap = new HashMap<>();
goodMap.put("goodId", goodId);
goodMap.put("goodName", "商品名称");
goodMap.put("price", 100); //商品价格
return goodMap;
},
taskExecutor
)
.exceptionally(error -> {
log.error("查询商品信息异常: {}", error.getMessage());
throw new RuntimeException("查询商品信息异常: " + error.getMessage());
});
stopWatch.stop();
//根据用户信息查询用户的优惠券信息
stopWatch.start("根据用户信息查询用户的优惠券信息");
CompletableFuture<Map<String, Object>> couponInfoFuture = userInfoFuture
.thenComposeAsync(
userMap -> {
log.info(
"根据用户信息查询用户的优惠券信息 线程: {}",
Thread.currentThread().getName()
);
Map<String, Object> couponMap = new HashMap<>();
couponMap.put("userId", userMap.get("userId"));
couponMap.put("coupon", "满100减10");
return CompletableFuture.completedFuture(couponMap);
},
taskExecutor
)
.exceptionally(error -> {
log.error("根据用户信息查询用户的优惠券信息异常: {}", error.getMessage());
throw new RuntimeException(
"根据用户信息查询用户的优惠券信息异常: " + error.getMessage()
);
});
stopWatch.stop();
//等待所有任务完成
stopWatch.start("等待所有任务完成");
CompletableFuture<Void> allFuture = CompletableFuture
.allOf(userInfoFuture, stockInfoFuture, goodInfoFuture, couponInfoFuture)
.thenAccept(v -> {
log.info("所有任务完成, 线程: {}", Thread.currentThread().getName());
});
allFuture.join(); //先调用join方法,等待所有任务完成
stopWatch.stop();
//获取所有任务的结果
Map<String, Object> userMap = userInfoFuture.join();
Map<String, Object> stockMap = stockInfoFuture.join();
Map<String, Object> goodMap = goodInfoFuture.join();
Map<String, Object> couponMap = couponInfoFuture.join();
log.info(
"所有任务完成, 用户信息: {}, 库存信息: {}, 商品信息: {}, 优惠券信息: {}",
userMap,
stockMap,
goodMap,
couponMap
);
log.info("购物流程耗时: {}ms", stopWatch.prettyPrint(TimeUnit.MICROSECONDS)); //微秒
}
3.2 循环创建任务
在<获取所有完成结果-allOf>章节中已经给出过循环创建任务的示例了。这里再给出一个示例:
/**
* 14. forTest 测试
*/
@Test
void forTest() {
StopWatch stopWatch = new StopWatch();
stopWatch.start("forTest");
// 循环创建10个CompletableFuture
List<CompletableFuture<Integer>> collect = IntStream
.range(1, 10)
.mapToObj(i ->
CompletableFuture
.supplyAsync(
() -> {
// 在i=5的时候抛出一个NPE
if (i == 5) {
throw new NullPointerException();
}
log.info(
"CompletableFuture 线程睡眠开始: {}, 结果: {}",
Thread.currentThread().getName(),
i
);
try {
// 每个依次睡眠1-9s,模拟线程耗时
TimeUnit.SECONDS.sleep(i);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.info(
"CompletableFuture 线程睡眠结束: {}, 结果: {}",
Thread.currentThread().getName(),
i
);
return i;
},
taskExecutor
)
.exceptionally(error -> {
// 这里处理一下i=5时出现的NPE
// 如果这里不处理异常,那么异常会在所有任务完成后抛出,小伙伴可自行测试
log.error("exceptionally 异常: {}", error.getMessage());
log.info(
"exceptionally 线程睡眠开始: {}, 结果: {}",
Thread.currentThread().getName(),
i
);
try {
TimeUnit.SECONDS.sleep(5);
System.out.println(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.info(
"exceptionally 线程睡眠结束: {}, 结果: {}",
Thread.currentThread().getName(),
i
);
return 100;
})
)
.collect(Collectors.toList());
// List列表转成CompletableFuture的Array数组,使其可以作为allOf()的参数
// 使用join()方法使得主线程阻塞,并等待所有并行线程完成
CompletableFuture.allOf(collect.toArray(new CompletableFuture[] {})).join();
stopWatch.stop();
log.info("forTest 耗时: {}ms", stopWatch.prettyPrint(TimeUnit.SECONDS)); //秒
}
输出结果:
2025-09-25 15:47:50.200 INFO 38440 --- \[r_thread_pool_1\] c.s.others.CompletableFutureComposeTest : CompletableFuture 线程睡眠开始: other_thread_pool_1, 结果: 1
2025-09-25 15:47:50.202 INFO 38440 --- \[r_thread_pool_6\] c.s.others.CompletableFutureComposeTest : CompletableFuture 线程睡眠开始: other_thread_pool_6, 结果: 6
2025-09-25 15:47:50.202 INFO 38440 --- \[r_thread_pool_3\] c.s.others.CompletableFutureComposeTest : CompletableFuture 线程睡眠开始: other_thread_pool_3, 结果: 3
2025-09-25 15:47:50.202 INFO 38440 --- \[r_thread_pool_2\] c.s.others.CompletableFutureComposeTest : CompletableFuture 线程睡眠开始: other_thread_pool_2, 结果: 2
2025-09-25 15:47:50.202 INFO 38440 --- \[r_thread_pool_7\] c.s.others.CompletableFutureComposeTest : CompletableFuture 线程睡眠开始: other_thread_pool_7, 结果: 7
2025-09-25 15:47:50.202 INFO 38440 --- \[r_thread_pool_4\] c.s.others.CompletableFutureComposeTest : CompletableFuture 线程睡眠开始: other_thread_pool_4, 结果: 4
2025-09-25 15:47:50.202 INFO 38440 --- \[r_thread_pool_9\] c.s.others.CompletableFutureComposeTest : CompletableFuture 线程睡眠开始: other_thread_pool_9, 结果: 9
2025-09-25 15:47:50.202 INFO 38440 --- \[r_thread_pool_8\] c.s.others.CompletableFutureComposeTest : CompletableFuture 线程睡眠开始: other_thread_pool_8, 结果: 8
2025-09-25 15:47:50.203 ERROR 38440 --- \[r_thread_pool_5\] c.s.others.CompletableFutureComposeTest : exceptionally 异常: java.lang.NullPointerException
2025-09-25 15:47:50.203 INFO 38440 --- \[r_thread_pool_5\] c.s.others.CompletableFutureComposeTest : exceptionally 线程睡眠开始: other_thread_pool_5, 结果: 5
2025-09-25 15:47:51.201 INFO 38440 --- \[r_thread_pool_1\] c.s.others.CompletableFutureComposeTest : CompletableFuture 线程睡眠结束: other_thread_pool_1, 结果: 1
2025-09-25 15:47:52.209 INFO 38440 --- \[r_thread_pool_2\] c.s.others.CompletableFutureComposeTest : CompletableFuture 线程睡眠结束: other_thread_pool_2, 结果: 2
2025-09-25 15:47:53.205 INFO 38440 --- \[r_thread_pool_3\] c.s.others.CompletableFutureComposeTest : CompletableFuture 线程睡眠结束: other_thread_pool_3, 结果: 3
2025-09-25 15:47:54.212 INFO 38440 --- \[r_thread_pool_4\] c.s.others.CompletableFutureComposeTest : CompletableFuture 线程睡眠结束: other_thread_pool_4, 结果: 4
100
2025-09-25 15:47:55.207 INFO 38440 --- \[r_thread_pool_5\] c.s.others.CompletableFutureComposeTest : exceptionally 线程睡眠结束: other_thread_pool_5, 结果: 5
2025-09-25 15:47:56.215 INFO 38440 --- \[r_thread_pool_6\] c.s.others.CompletableFutureComposeTest : CompletableFuture 线程睡眠结束: other_thread_pool_6, 结果: 6
2025-09-25 15:47:57.204 INFO 38440 --- \[r_thread_pool_7\] c.s.others.CompletableFutureComposeTest : CompletableFuture 线程睡眠结束: other_thread_pool_7, 结果: 7
2025-09-25 15:47:58.210 INFO 38440 --- \[r_thread_pool_8\] c.s.others.CompletableFutureComposeTest : CompletableFuture 线程睡眠结束: other_thread_pool_8, 结果: 8
2025-09-25 15:47:59.213 INFO 38440 --- \[r_thread_pool_9\] c.s.others.CompletableFutureComposeTest : CompletableFuture 线程睡眠结束: other_thread_pool_9, 结果: 9
2025-09-25 15:47:59.224 INFO 38440 --- \[ main\] c.s.others.CompletableFutureComposeTest : forTest 耗时: StopWatch '': running time = 9 s
\---------------------------------------------
s % Task name
\---------------------------------------------
000000009 100% forTest
ms
4. 使用场景总结
1.并行执行任务
- 场景:多个互不依赖的任务,可以并行执行,加快整体耗时。
- 方法:supplyAsync + allOf / anyOf
- 例子:并行查询用户信息、库存信息、商品信息。
CompletableFuture user = CompletableFuture.supplyAsync(() -> getUser());
CompletableFuture stock = CompletableFuture.supplyAsync(() -> getStock());
CompletableFuture product = CompletableFuture.supplyAsync(() -> getProduct());
CompletableFuture all = CompletableFuture.allOf(user, stock, product);
all.join(); // 等所有完成
2.串行依赖任务
- 场景:下一个任务依赖上一个任务的结果。
- 方法:thenCompose
- 例子:先查询用户,再根据用户查优惠券。
CompletableFuture userFuture = CompletableFuture.supplyAsync(() -> getUser());
CompletableFuture couponFuture = userFuture.thenCompose(user ->
CompletableFuture.supplyAsync(() -> getCoupon(user))
);
3.结果转换
- 场景:拿到结果后进行加工处理。
- 方法:thenApply
- 例子:获取商品后,转换成展示用 DTO。
CompletableFuture productFuture = CompletableFuture.supplyAsync(() -> getProduct());
CompletableFuture dtoFuture = productFuture.thenApply(this::convertToDTO);
4.结果消费
- 场景:只需要用结果,不关心返回值。
- 方法:thenAccept / thenRun
- 例子:记录日志、写数据库、消息通知。
productFuture.thenAccept(product -> log.info("商品: {}", product));
5.组合任务结果
- 场景:需要两个任务的结果才能继续处理。
- 方法:thenCombine / thenAcceptBoth / runAfterBoth
- 例子:库存结果 + 优惠券结果 → 计算最终价格。
CompletableFuture priceFuture = productFuture.thenCombine(couponFuture,
(product, coupon) -> product.getPrice() - coupon.getDiscount()
);
6.竞争执行
- 场景:多个任务,只要最快的那个完成就行。
- 方法:applyToEither / acceptEither / runAfterEither
- 例子:调用两个远程服务,谁先返回用谁。
CompletableFuture fast = serviceA.applyToEither(serviceB, res -> res);
7.异常处理
- 场景:任务执行可能失败,需要兜底。
- 方法:handle / exceptionally / whenComplete
- 例子:接口超时,返回默认值。
CompletableFuture safeFuture = remoteCall.exceptionally(ex -> {
log.error("远程调用失败: {}", ex.getMessage());
return "默认值";
});
8.主动控制任务结果
- 场景:外部逻辑需要手动完成或中断任务。
- 方法:complete / completeExceptionally
- 例子:超时后返回默认结果。
CompletableFuture future = new CompletableFuture<>();
scheduler.schedule(() -> future.complete("超时返回"), 2, TimeUnit.SECONDS);
9.统一收尾
- 场景:不管成功还是失败,都要做一些清理或日志。
- 方法:whenComplete
- 例子:RPC 调用后记录耗时。
future.whenComplete((res, ex) -> {
long cost = System.currentTimeMillis() - start;
log.info("耗时: {} ms", cost);
});
📌 总结口诀
- 并行 → allOf
- 串行依赖 → thenCompose
- 结果转换 → thenApply
- 结果消费 → thenAccept / thenRun
- 结果合并 → thenCombine
- 竞争取快 → applyToEither
- 异常兜底 → exceptionally / handle
- 手动完成 → complete
- 统一收尾 → whenComplete

浙公网安备 33010602011771号