Java 异步编程工具类 CompletableFuture 详细介绍

java-青灯文案

CompletableFuture 是 Java 8 引入的异步编程工具类,实现了 Future 和 CompletionStage 接口,弥补了传统 Future 的局限性(如无法链式操作、缺乏异常处理、难以组合多个异步任务等)。它支持异步任务的创建、链式执行、结果组合、异常处理等,是处理多线程异步场景的核心工具。

Future 接口的使用和局限性可以查看:Java 异步编程接口 Future 详细介绍

一、CompletableFuture 简介

CompletableFuture 为 Java 提供了强大的异步编程能力,可以极大地提高应用的并发能力和响应速度。同时,CompletableFuture 代码风格是使用函数式编程,简洁和优雅同时也使得代码更加易读易维护。

CompletableFuture 是一个异步编程工具类,主要功能就是解决某些响应时间过长的业务或者含有并发查询的业务,面对这种业务问题我们常常使用多线程的方式解决,但是多线程同时会带来一些问题:

  • 创建和管理线程的开销较大,如果线程数量过多,会给系统带来压力。
  • 如果查询任务的执行时间不均匀,会导致部分线程需要长时间等待,资源利用率低下。

CompletableFuture 异步编程模型可以很好地解决这个问题,该模型可以做到多个任务同时执行,互不影响,从而大幅提高应用的响应速度和吞吐量。我们可以利用模型的方法做到每个操作各自异步执行,等所有操作完成后组装结果。

  • 将任务都封装为一个 CompletableFuture 异步任务,由线程池并行执行。
  • 通过 CompletableFuture.allOf() 方法等待所有异步任务完成,然后从所有结果中组装出最终需要的数据对象。

1、CompletableFuture实现了 FutureCompletionStage 接口。

  • Future 接口:提供了对异步任务的基本控制能力,如获取结果、取消任务等。
  • CompletionStage 接口:代表异步任务的一个阶段,一个阶段完成后,可触发下一个阶段(如转换结果、消费结果等)。
  • 每个 CompletableFuture 有三种状态:状态一旦完成(正常/异常),就不可再改变。
    • 未完成(Incomplete)。
    • 正常完成(Completed Normally)。
    • 异常完成(Completed Exceptionally)。

2、CompletableFuture 支持异步任务的创建、链式执行、结果组合、异常处理等,是处理多线程异步场景的核心工具。

  • 异步执行:支持在独立线程中执行任务,无需阻塞主线程。
  • 链式操作:任务执行完成后可自动触发后续操作(如结果处理、转换)。
  • 任务组合:支持将多个异步任务按逻辑组合(两个任务都完成后执行、任一任务完成后执行)。
  • 异常处理:提供专门的异常捕获和处理机制,避免异步任务无声失败。

以上几点会在下面用代码详细解释。

二、CompletableFuture 方法汇总

以下展示的所有方法,下面都会有对应的代码解释。

1、创建任务方法

方法作用描述
supplyAsync(Supplier<T> supplier)异步执行有返回值的任务(使用默认线程池 ForkJoinPool.commonPool()
runAsync(Runnable runnable)异步执行无返回值的任务(使用默认线程池),使用自定义线程池需要设置,如下面代码所示
completedFuture(T value)创建一个已完成的 CompletableFuture,直接返回指定结果

2、手动完成方法

方法作用描述
complete(T value)手动设置任务正常完成的结果(若已完成,此操作无效)
completeExceptionally(Throwable ex)手动设置任务异常完成(触发异常,若已完成,此操作无效)

3、链式操作方法

方法作用描述
thenApply()接收前序任务结果,转换后返回新结果(同步执行,使用前序任务线程或当前线程)
thenApplyAsync()同上,但异步执行(使用默认线程池),使用自定义线程池需要设置,如下面代码所示
thenAccept()接收前序任务结果并消费(无返回值,同步执行)
thenAcceptAsync()同上,但异步执行(默认线程池),使用自定义线程池需要设置,如下面代码所示
thenRun()前序任务完成后执行无参操作(与前序结果无关,同步执行)
thenRunAsync()同上,但异步执行(默认线程池),使用自定义线程池需要设置,如下面代码所示

4、任务组合方法

方法作用描述
thenCombine()等待当前任务和其他任务都完成后,合并结果并返回新结果(同步)
thenCombineAsync()同上,但异步执行(默认线程池),使用自定义线程池需要设置,如下面代码所示
thenAcceptBoth()等待当前任务和 other 任务都完成后,消费合并后的结果(无返回值,同步)
thenAcceptBothAsync()同上,但异步执行(默认线程池),使用自定义线程池需要设置,如下面代码所示
runAfterBoth()等待当前任务和 other 任务都完成后,执行无参操作(同步)
applyToEither()取当前任务和 other 任务中先完成的结果,转换后返回新结果(同步)
applyToEitherAsync(同上,但异步执行(默认线程池),使用自定义线程池需要设置,如下面代码所示
acceptEither()取当前任务和 other 任务中先完成的结果并消费(无返回值,同步)
runAfterEither()当前任务或 other 任务任一完成后,执行无参操作(同步)
allOf()等待所有传入的 CompletableFuture 完成(返回 CompletableFuture<Void>,无结果)
anyOf()等待任一传入的 CompletableFuture 完成(返回 CompletableFuture<Object>,结果为最先完成的任务结果)

5、异常处理方法

方法作用描述
exceptionally()任务异常时,执行回调函数返回默认结果(不影响正常流程)
handle()无论任务正常或异常完成,都执行回调(参数为结果和异常,返回新结果)
whenComplete()无论任务正常或异常完成,都执行回调(仅消费结果/异常,不改变原有结果)
whenCompleteAsync()同上,但异步执行(默认线程池),使用自定义线程池需要设置,如下面代码所示

6、获取结果方法

方法作用描述
get()阻塞等待任务完成,返回结果(异常时抛出 ExecutionException
get(long timeout, TimeUnit unit)带超时的阻塞等待,超时未完成则抛出 TimeoutException
join()类似 get(),但异常时直接抛出 unchecked 异常(无需捕获 ExecutionException
  • 方法名含 Async 后缀的为异步版本,可指定线程池(默认使用 ForkJoinPool.commonPool()),不含 Async 的为同步版本(可能在当前线程或前序任务线程中执行)。
  • 组合类方法(如 thenCombineallOf)用于多任务协作,是 CompletableFuture 相比传统 Future 的核心优势。
  • 异常处理方法(exceptionallyhandle 等)避免了异步任务异常“无声失败”的问题,简化了错误处理逻辑。

二、CompletableFuture 基础使用

1、异步执行有返回值的任务:supplyAsync

CompletableFuture 默认是使用线程池执行,如果没有指定,会使用默认线程池 ForkJoinPool.commonPool()

// 使用默认线程池(ForkJoinPool.commonPool())
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
    // 异步任务:比如查询数据库、调用接口
    try { Thread.sleep(1000); } catch (InterruptedException e) { throw new RuntimeException(e); }
    return "任务结果";
});

// 自定义线程池(推荐,避免默认线程池资源耗尽)
ExecutorService executor = Executors.newFixedThreadPool(3);
CompletableFuture<String> futureWithExecutor = CompletableFuture.supplyAsync(() -> {
    return "使用自定义线程池的结果";
}, executor);

executor.shutdown();
  • 入参:Supplier<T>(无参有返回值的函数),可选自定义线程池。
  • 返回:CompletableFuture<T>,结果类型为 T

2、异步执行无返回值的任务:runAsync

// 无返回值,适合执行“只做动作不产结果”的任务(如日志记录、发送通知)
CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
    try { Thread.sleep(1000); } catch (InterruptedException e) { throw new RuntimeException(e); }
    System.out.println("任务执行完成");
});
  • 入参:Runnable(无参无返回值),可选自定义线程池。
  • 返回:CompletableFuture<Void>

3、手动完成任务:complete / completeExceptionally

用于外部触发任务完成的场景(如模拟结果、中断任务):

CompletableFuture<String> future = new CompletableFuture<>();

// 手动设置正常结果(若已完成,此操作无效)
future.complete("手动设置的结果");

// 手动设置异常结果(触发异常完成)
future.completeExceptionally(new RuntimeException("任务失败"));

三、链式操作(CompletionStage 核心方法)

当一个 CompletableFuture 完成后,可通过以下方法触发后续操作,形成流水线

1、处理结果并返回新结果:thenApply

对前一个任务的结果进行转换,生成新结果(有返回值):

CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> "hello")
    // 前一个任务完成后,将结果转为大写
    .thenApply(result -> result.toUpperCase());

System.out.println(future.get()); // 输出:HELLO
  • 入参:Function<T, U>(接收前一个结果 T,返回新结果 U)。
  • 执行线程:默认与前一个任务同线程,或使用前一个任务的线程池(可通过 async 版本指定新线程池,如 thenApplyAsync)。

2、消费结果不返回:thenAccept

接收前一个任务的结果并消费(无返回值):

CompletableFuture.supplyAsync(() -> "hello")
    .thenAccept(result -> System.out.println("消费结果:" + result)); // 输出:消费结果:hello
  • 入参:Consumer<T>(接收前一个结果 T,无返回)。

3、不依赖前序结果的后续操作:thenRun

前一个任务完成后,执行一个无参无返回的操作(与前序结果无关):

CompletableFuture.supplyAsync(() -> "hello")
    .thenRun(() -> System.out.println("前序任务已完成")); // 输出:前序任务已完成
  • 入参:Runnable(不依赖前序结果)。

4、异步版本:thenApplyAsync / thenAcceptAsync / thenRunAsync

默认版本的链式操作可能在前序任务的线程中执行(若前序任务已完成,则在当前线程执行)。async 版本强制在指定线程池(或默认线程池)中异步执行:

ExecutorService executor = Executors.newSingleThreadExecutor();

CompletableFuture.supplyAsync(() -> "hello")
    // 使用自定义线程池异步处理
    .thenApplyAsync(result -> result.toUpperCase(), executor)
    .thenAcceptAsync(result -> System.out.println(result), executor); // 输出:HELLO

四、组合多个 CompletableFuture

实际场景中常需组合多个异步任务,如并行调用两个接口,再合并结果,CompletableFuture 提供了丰富的组合方法。

1、两个任务都完成后执行:thenCombine / thenAcceptBoth / runAfterBoth

1、thenCombine:两个任务完成后,合并结果并返回新结果。

// 任务1:获取用户ID
CompletableFuture<Long> userIdFuture = CompletableFuture.supplyAsync(() -> 1001L);
// 任务2:获取用户名
CompletableFuture<String> userNameFuture = CompletableFuture.supplyAsync(() -> "张三");

// 合并结果:生成用户信息
CompletableFuture<String> userInfoFuture = userIdFuture.thenCombine(
	userNameFuture, 
	(userId, userName) -> "用户ID:" + userId + ",用户名:" + userName
);

System.out.println(userInfoFuture.get()); // 输出:用户ID:1001,用户名:张三

2、thenAcceptBoth:两个任务完成后,消费合并后的结果(无返回)。

userIdFuture.thenAcceptBoth(userNameFuture, 
	(userId, userName) -> System.out.println("用户信息:" + userId + "," + userName)
);

3、runAfterBoth:两个任务完成后,执行一个无关操作(无参无返回)。

userIdFuture.runAfterBoth(userNameFuture, 
	() -> System.out.println("两个用户任务都已完成")
);

2、任一任务完成后执行:applyToEither / acceptEither / runAfterEither

适用于取两个任务中先完成的结果的场景(如备用接口调用)。

1、applyToEither:取先完成的任务结果,转换后返回。这里有一个注意点,因为 applyToEither 处理两个任务的结果,所以要确保这两个任务的结果是一样的对象,才能处理成最后的结果。

// 任务1:快任务(1秒完成)
CompletableFuture<String> fastTask = CompletableFuture.supplyAsync(() -> {
	try { Thread.sleep(1000); } catch (InterruptedException e) { throw new RuntimeException(e); }
	return "快任务结果";
});

// 任务2:慢任务(2秒完成)
CompletableFuture<String> slowTask = CompletableFuture.supplyAsync(() -> {
	try { Thread.sleep(2000); } catch (InterruptedException e) { throw new RuntimeException(e); }
	return "慢任务结果";
});

// 取先完成的任务结果,加前缀后返回
CompletableFuture<String> resultFuture = fastTask.applyToEither(
	slowTask, 
	result -> "先完成的结果:" + result
);

System.out.println(resultFuture.get()); // 1秒后输出:先完成的结果:快任务结果

2、acceptEither:消费先完成的任务结果(无返回)。 这个方法也要保证两个任务的结果是一致的,即范型一样。

// 任务1:快任务(1秒完成)
CompletableFuture<String> fastTask = CompletableFuture.supplyAsync(() -> {
	try { Thread.sleep(1000); } catch (InterruptedException e) { throw new RuntimeException(e); }
	return "快任务结果";
});

// 任务2:慢任务(2秒完成)
CompletableFuture<String> slowTask = CompletableFuture.supplyAsync(() -> {
	try { Thread.sleep(2000); } catch (InterruptedException e) { throw new RuntimeException(e); }
	return "慢任务结果";
});

// 取先完成的任务结果,做操作
fastTask.acceptEither(slowTask, result -> System.out::println);

3、runAfterEither:任一任务完成后,执行无关操作。这里是执行无关操作,不跟据返回结果类型,所以不用要求任务范型一致。

// 任务1:快任务(1秒完成)
CompletableFuture<Long> fastTask = CompletableFuture.supplyAsync(() -> {
	try { Thread.sleep(1000); } catch (InterruptedException e) { throw new RuntimeException(e); }
	return 100L;
});

// 任务2:慢任务(2秒完成)
CompletableFuture<String> slowTask = CompletableFuture.supplyAsync(() -> {
	try { Thread.sleep(2000); } catch (InterruptedException e) { throw new RuntimeException(e); }
	return "慢任务结果";
});

// 取先完成的任务结果,做操作
fastTask.runAfterEither(slowTask, () -> System.out.println("存在完成任务"));

3、多个任务组合:allOf / anyOf

1、allOf:等待所有任务完成(无返回值,需手动获取每个任务结果)。

CompletableFuture<Void> task1 = CompletableFuture.runAsync(() -> { ... });
CompletableFuture<String> task2 = CompletableFuture.supplyAsync(() -> "结果2");
CompletableFuture<Integer> task3 = CompletableFuture.supplyAsync(() -> 3);

// 等待所有任务完成
CompletableFuture<Void> allDone = CompletableFuture.allOf(task1, task2, task3);

// 所有任务完成后,获取单个结果
allDone.thenRun(() -> {
	try {
		String res2 = task2.get();
		int res3 = task3.get();
		System.out.println("任务2结果:" + res2 + ",任务3结果:" + res3);
	} catch (Exception e) { ... }
});

2、anyOf:等待任一任务完成(返回最先完成的任务结果,类型为 Object)。

CompletableFuture<Object> anyDone = CompletableFuture.anyOf(task1, task2, task3);
anyDone.thenAccept(result -> System.out.println("最先完成的结果:" + result));

五、异常处理

CompletableFuture 提供了多种异常处理方式,避免异步任务异常被忽略。

1、exceptionally:捕获异常并返回默认值

类似 try-catch,当任务异常完成时,返回一个默认结果(不改变正常流程)。

CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
    if (true) throw new RuntimeException("任务失败");
    return "正常结果";
})
// 捕获异常,返回默认值
.exceptionally(ex -> {
    System.out.println("捕获异常:" + ex.getMessage());
    return "默认结果";
});

System.out.println(future.get()); // 输出:默认结果

2、handle:同时处理正常结果和异常

无论任务正常完成还是异常完成,都能处理(类似 try-finally 的增强版)。

CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
    // 模拟:50%概率成功
    if (Math.random() > 0.5) throw new RuntimeException("失败");
    return "成功结果";
})
.handle((result, ex) -> {
    if (ex != null) {
        return "处理异常:" + ex.getMessage();
    } else {
        return "处理正常结果:" + result;
    }
});

3、whenComplete:处理结果 / 异常但不改变结果

仅用于副作用操作(如日志记录),不影响原有结果或异常。

CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> "正常结果")
    .whenComplete((result, ex) -> {
        if (ex != null) {
            System.out.println("异常日志:" + ex.getMessage());
        } else {
            System.out.println("正常日志:结果是" + result);
        }
    });
// 结果仍为"正常结果",whenComplete不改变结果

六、注意事项

1、线程池使用

  • 默认线程池supplyAsync/runAsync 若不指定线程池,默认使用 ForkJoinPool.commonPool(),其线程数与 CPU 核心数相关(Runtime.getRuntime().availableProcessors())。
    • 高并发场景下可能因资源不足导致阻塞,建议使用自定义线程池
  • 线程池隔离:不同业务的异步任务应使用独立线程池,避免相互干扰(如订单任务和日志任务分开)。
  • 线程池关闭:自定义线程池需在使用后手动关闭(executor.shutdown()),避免资源泄漏。

2、适用场景

  • 并行任务处理:如微服务中并行调用多个接口,合并结果后返回。
  • 异步通知:如订单创建后,异步发送短信/邮件(不阻塞主流程)。
  • 超时控制:结合 get(long timeout, TimeUnit unit) 实现任务超时处理。
  • 复杂流程编排:如任务A完成后执行B和C,B和C都完成后执行D。

CompletableFuture 是 Java 异步编程的核心工具,通过链式操作、任务组合、异常处理三大能力,大幅简化了多线程异步场景的代码复杂度。相比传统 Future,它更灵活、更强大,是处理并发任务的首选方案。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值