CompletableFuture 异步编程完全指南:从基础到实战
本文介绍 Java CompletableFuture 异步编程核心用法。内容涵盖任务创建(runAsync/supplyAsync)、结果链式处理(thenApply/thenAccept/thenRun)、多任务组合(thenCompose/thenCombine/allOf/anyOf)及异常处理策略。重点强调使用自定义线程池避免资源耗尽,并对比 get() 与 join() 的区别,提供实战中的最佳实践建议。

本文介绍 Java CompletableFuture 异步编程核心用法。内容涵盖任务创建(runAsync/supplyAsync)、结果链式处理(thenApply/thenAccept/thenRun)、多任务组合(thenCompose/thenCombine/allOf/anyOf)及异常处理策略。重点强调使用自定义线程池避免资源耗尽,并对比 get() 与 join() 的区别,提供实战中的最佳实践建议。

在 Java 异步编程领域,CompletableFuture 绝对是'瑞士军刀'级别的工具——它解决了传统 Future 无法链式调用、缺乏异常处理、难以组合多任务的痛点,让异步代码变得简洁、可读且易维护。无论是处理单个异步任务的结果,还是协调多个任务的依赖关系,CompletableFuture 都能轻松应对。本文将从基础用法到实战技巧,帮你彻底掌握 CompletableFuture 的核心能力。
CompletableFuture 的入门操作是创建异步任务,分为无返回值和有返回值两种场景,对应两个核心方法:runAsync 和 supplyAsync。
适合只需要执行操作、不需要返回结果的场景(比如日志记录、文件备份),底层基于 Runnable 接口实现。
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class CompletableFutureDemo {
public static void main(String[] args) throws InterruptedException {
// 1. 默认线程池(不推荐):使用 ForkJoinPool.commonPool(),易耗尽资源
CompletableFuture<Void> future1 = CompletableFuture.runAsync(() -> {
System.out.println("无返回值任务 1,线程:" + Thread.currentThread().getName());
Thread.sleep(1000); // 模拟耗时操作
});
// 2. 自定义线程池(推荐):控制线程数量,避免资源耗尽
ExecutorService executor = Executors.newFixedThreadPool(2);
CompletableFuture<Void> future2 = CompletableFuture.runAsync(() -> {
System.out.println("无返回值任务 2,线程:" + Thread.currentThread().getName());
Thread.sleep(1000);
}, executor);
// 主线程等待任务完成(否则 JVM 提前退出)
Thread.sleep(2000);
executor.shutdown(); // 关闭自定义线程池
}
}
适合需要异步执行并返回结果的场景(比如调用远程接口、查询数据库),底层基于 Supplier 接口实现,是最常用的创建方式。
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
public class CompletableFutureDemo {
public static void main(String[] args) throws Exception {
ExecutorService executor = Executors.newSingleThreadExecutor();
// 创建有返回值的异步任务
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
System.out.println("有返回值任务,线程:" + Thread.currentThread().getName());
TimeUnit.SECONDS.sleep(1); // 模拟耗时 1 秒
return "异步任务执行结果"; // 返回结果
}, executor);
// 阻塞获取结果(后续会讲更优雅的链式处理)
String result = future.get();
System.out.println("获取到的结果:" + result);
executor.shutdown();
}
}
有返回值任务,线程:pool-1-thread-1 获取到的结果:异步任务执行结果
CompletableFuture 的灵魂优势是链式调用——任务完成后,无需阻塞等待结果,直接通过回调方法处理。常用的三个方法可以用'输入输出规则'快速区分:
| 方法名 | 输入(是否接收任务结果) | 输出(是否返回新结果) | 适用场景 |
|---|---|---|---|
| thenApply | 是 | 是 | 加工结果并返回新值(比如字符串拼接) |
| thenAccept | 是 | 否 | 消费结果(比如打印日志) |
| thenRun | 否 | 否 | 任务完成后做收尾(比如发送通知) |
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executors;
public class CompletableFutureDemo {
public static void main(String[] args) throws Exception {
// 1. 创建有返回值的初始任务
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> "原始结果", Executors.newSingleThreadExecutor());
// 2. thenApply:加工结果(原始结果 -> 加工后结果)
CompletableFuture<String> applyFuture = future.thenApply(result -> result + " -> 经过 thenApply 处理");
// 3. thenAccept:消费结果(打印加工后的结果)
CompletableFuture<Void> acceptFuture = applyFuture.thenAccept(finalResult -> System.out.println("thenAccept 消费结果:" + finalResult));
// 4. thenRun:任务完成后收尾(打印完成提示)
CompletableFuture<Void> runFuture = acceptFuture.thenRun(() -> System.out.println("所有任务完成,执行 thenRun"));
// 等待最终任务完成
runFuture.get();
}
}
thenAccept 消费结果:原始结果 -> 经过 thenApply 处理 所有任务完成,执行 thenRun
实际开发中,异步任务往往不是孤立的——要么串行依赖(一个任务的输入需要另一个任务的输出),要么并行执行(多个任务同时进行,最后合并结果),要么批量处理(等待所有任务完成或任意一个完成)。CompletableFuture 提供了针对性的解决方案。
当任务 B 的输入依赖任务 A 的输出时,用 thenCompose 代替嵌套的 CompletableFuture,让代码更扁平。
模拟'根据用户 ID 查用户名→根据用户名查用户信息'的串行流程。
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executors;
public class CompletableFutureDemo {
// 模拟根据用户 ID 获取用户名(返回 CompletableFuture)
private static CompletableFuture<String> getUsernameById(Integer id) {
return CompletableFuture.supplyAsync(() -> {
System.out.println("获取用户名,线程:" + Thread.currentThread().getName());
return "用户" + id; // 比如用户 1→"用户 1"
}, Executors.newSingleThreadExecutor());
}
// 模拟根据用户名获取用户信息(返回 CompletableFuture)
private static CompletableFuture<String> getUserInfoByName(String name) {
return CompletableFuture.supplyAsync(() -> {
System.out.println("获取用户信息,线程:" + Thread.currentThread().getName());
return name + " - 年龄 20"; // 比如"用户 1"→"用户 1 - 年龄 20"
}, Executors.newSingleThreadExecutor());
}
public static void main(String[] args) throws Exception {
// 串行调用:getUsernameById → getUserInfoByName
CompletableFuture<String> finalFuture = getUsernameById(1).thenCompose(name -> getUserInfoByName(name));
// 关键:thenCompose 扁平化调用
System.out.println("最终结果:" + finalFuture.get());
}
}
获取用户名,线程:pool-1-thread-1 获取用户信息,线程:pool-2-thread-1 最终结果:用户 1 - 年龄 20
当两个任务并行执行,都完成后需要合并结果时,用 thenCombine(比如查商品价格和折扣,计算最终价格)。
模拟'查商品价格→查折扣→计算最终价格'的并行流程。
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executors;
public class CompletableFutureDemo {
// 模拟获取商品价格
private static CompletableFuture<Double> getPrice(String product) {
return CompletableFuture.supplyAsync(() -> {
System.out.println("获取" + product + "价格,线程:" + Thread.currentThread().getName());
return 100.0; // 商品价格 100 元
}, Executors.newSingleThreadExecutor());
}
// 模拟获取商品折扣
private static CompletableFuture<Double> getDiscount() {
return CompletableFuture.supplyAsync(() -> {
System.out.println("获取折扣,线程:" + Thread.currentThread().getName());
return 0.8; // 8 折
}, Executors.newSingleThreadExecutor());
}
public static void main(String[] args) throws Exception {
// 并行执行两个任务,合并结果(价格 * 折扣)
CompletableFuture<Double> finalPriceFuture = getPrice("手机").thenCombine(getDiscount(), (price, discount) -> price * discount);
System.out.println("最终价格:" + finalPriceFuture.get());
}
}
获取手机价格,线程:pool-1-thread-1 获取折扣,线程:pool-2-thread-1 最终价格:80.0
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executors;
public class CompletableFutureDemo {
// 模拟一个延迟完成的任务(延迟时间=num*1 秒)
private static CompletableFuture<String> task(Integer num) {
return CompletableFuture.supplyAsync(() -> {
try {
Thread.sleep(num * 1000);
} catch (InterruptedException e) {
}
System.out.println("任务" + num + "完成");
return "结果" + num;
}, Executors.newFixedThreadPool(3)); // 固定 3 个线程处理批量任务
}
public static void main(String[] args) throws Exception {
// 1. allOf:等待所有任务完成(任务 1、2、3 都完成后打印提示)
CompletableFuture<Void> allFuture = CompletableFuture.allOf(task(1), task(2), task(3));
allFuture.get(); // 阻塞等待所有任务完成
System.out.println("===== 所有任务都完成了 =====");
// 2. anyOf:等待任意一个任务完成(取第一个完成的任务结果)
CompletableFuture<Object> anyFuture = CompletableFuture.anyOf(task(1), task(2), task(3));
System.out.println("第一个完成的任务结果:" + anyFuture.get());
}
}
任务 1 完成 任务 2 完成 任务 3 完成 ===== 所有任务都完成了 ===== 任务 1 完成 第一个完成的任务结果:结果 1
异步任务的异常很'隐蔽'——如果不主动处理,它不会像同步代码那样直接抛出,而是悄悄消失。CompletableFuture 提供了三种核心异常处理方式,覆盖不同场景:
当任务抛出异常时,返回一个默认值(比如接口调用失败时返回空列表)。
模拟'任务执行时抛出除以零异常',用 exceptionally 返回兜底值。
无论任务是正常完成还是抛出异常,都能统一处理并返回新结果(比如正常时返回结果,异常时返回错误信息)。
无论任务是否异常,都能消费结果或异常信息(比如打印日志),但不返回新结果。
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executors;
public class CompletableFutureDemo {
public static void main(String[] args) throws Exception {
// 模拟一个会抛出异常的异步任务(1/0)
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
System.out.println("执行异步任务");
int a = 1 / 0; // 模拟异常
return "正常结果";
}, Executors.newSingleThreadExecutor());
// 1. exceptionally:异常时返回兜底值("异常兜底值")
CompletableFuture<String> exceptionFuture = future.exceptionally(e -> {
System.out.println("捕获异常:" + e.getMessage());
return "异常兜底值";
});
// 2. handle:处理所有情况(正常/异常)并返回结果
CompletableFuture<String> handleFuture = exceptionFuture.handle((result, e) -> {
if (e != null) {
return "handle 处理异常:" + e.getMessage(); // 异常时返回错误信息
} else {
return "handle 处理正常:" + result; // 正常时返回结果
}
});
// 3. whenComplete:消费最终结果(打印结果)
CompletableFuture<Void> whenCompleteFuture = handleFuture.whenComplete((result, e) -> {
System.out.println("whenComplete 最终结果:" + result);
});
whenCompleteFuture.get();
}
}
执行异步任务 捕获异常:/ by zero whenComplete 最终结果:handle 处理异常:/ by zero
获取 CompletableFuture 的结果有两种常用方式,核心区别是异常处理:
| 方法名 | 异常类型 | 适用场景 |
|---|---|---|
| get() | 抛出检查异常(需要 try-catch 或 throws 声明) | 同步代码中需要明确处理异常的场景 |
| join() | 抛出非检查异常(不需要 try-catch) | Lambda 表达式或链式调用中,简化代码 |
import java.util.concurrent.CompletableFuture;
public class CompletableFutureDemo {
public static void main(String[] args) {
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> "结果");
// 1. get():需要捕获异常
try {
String result1 = future.get();
System.out.println("get 获取结果:" + result1);
} catch (Exception e) {
e.printStackTrace();
}
// 2. join():无需捕获异常(更适合 Lambda)
String result2 = future.join();
System.out.println("join 获取结果:" + result2);
}
}
get 获取结果:结果 join 获取结果:结果
通过本文的讲解,CompletableFuture 的常用能力可以归纳为 5 个核心点,直接对照使用即可:
掌握 CompletableFuture,不是为了'炫技',而是为了写出更高效、可维护的代码。

微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online
JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online
使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online
Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online