跳到主要内容CompletableFuture 异步编程完全指南:从基础到实战 | 极客日志Javajava
CompletableFuture 异步编程完全指南:从基础到实战
介绍 Java CompletableFuture 异步编程核心用法。内容涵盖任务创建(runAsync/supplyAsync)、结果链式处理(thenApply/thenAccept/thenRun)、多任务组合(thenCompose/thenCombine/allOf/anyOf)及异常处理策略。重点强调使用自定义线程池避免资源耗尽,并对比 get() 与 join() 的区别,提供实战中的最佳实践建议。
草莓泡芙36 浏览 CompletableFuture 异步编程完全指南:从基础到实战
在 Java 异步编程领域,CompletableFuture 绝对是'瑞士军刀'级别的工具——它解决了传统 Future 无法链式调用、缺乏异常处理、难以组合多任务的痛点,让异步代码变得简洁、可读且易维护。无论是处理单个异步任务的结果,还是协调多个任务的依赖关系,CompletableFuture 都能轻松应对。本文将从基础用法到实战技巧,帮你彻底掌握 CompletableFuture 的核心能力。
一、基础:如何创建异步任务?
CompletableFuture 的入门操作是创建异步任务,分为无返回值和有返回值两种场景,对应两个核心方法:runAsync 和 supplyAsync。
1. 无返回值任务:runAsync
适合只需要执行操作、不需要返回结果的场景(比如日志记录、文件备份),底层基于 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 {
CompletableFuture<Void> future1 = CompletableFuture.runAsync(() -> {
System.out.println("无返回值任务 1,线程:" + Thread.currentThread().getName());
Thread.sleep(1000);
});
ExecutorService executor = Executors.newFixedThreadPool(2);
CompletableFuture<Void> future2 = CompletableFuture.runAsync(() -> {
System.out.println("无返回值任务 2,线程:" + Thread.currentThread().getName());
Thread.sleep(1000);
}, executor);
Thread.sleep(2000);
executor.shutdown();
}
}
关键提醒:
- !默认线程池是 JVM 共享的 ForkJoinPool.commonPool(),若大量任务同时执行,容易耗尽线程资源导致系统崩溃。
务必使用自定义线程池
2. 有返回值任务:supplyAsync
适合需要异步执行并返回结果的场景(比如调用远程接口、查询数据库),底层基于 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);
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 {
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> "原始结果", Executors.newSingleThreadExecutor());
CompletableFuture<String> applyFuture = future.thenApply(result -> result + " -> 经过 thenApply 处理");
CompletableFuture<Void> acceptFuture = applyFuture.thenAccept(finalResult -> System.out.println("thenAccept 消费结果:" + finalResult));
CompletableFuture<Void> runFuture = acceptFuture.thenRun(() -> System.out.println("所有任务完成,执行 thenRun"));
runFuture.get();
}
}
输出结果:
thenAccept 消费结果:原始结果 -> 经过 thenApply 处理 所有任务完成,执行 thenRun
三、进阶:多任务组合的实战技巧
实际开发中,异步任务往往不是孤立的——要么串行依赖(一个任务的输入需要另一个任务的输出),要么并行执行(多个任务同时进行,最后合并结果),要么批量处理(等待所有任务完成或任意一个完成)。CompletableFuture 提供了针对性的解决方案。
1. 串行依赖:thenCompose(避免嵌套地狱)
当任务 B 的输入依赖任务 A 的输出时,用 thenCompose 代替嵌套的 CompletableFuture,让代码更扁平。
场景示例:
模拟'根据用户 ID 查用户名→根据用户名查用户信息'的串行流程。
代码示例:
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executors;
public class CompletableFutureDemo {
private static CompletableFuture<String> getUsernameById(Integer id) {
return CompletableFuture.supplyAsync(() -> {
System.out.println("获取用户名,线程:" + Thread.currentThread().getName());
return "用户" + id;
}, Executors.newSingleThreadExecutor());
}
private static CompletableFuture<String> getUserInfoByName(String name) {
return CompletableFuture.supplyAsync(() -> {
System.out.println("获取用户信息,线程:" + Thread.currentThread().getName());
return name + " - 年龄 20";
}, Executors.newSingleThreadExecutor());
}
public static void main(String[] args) throws Exception {
CompletableFuture<String> finalFuture = getUsernameById(1).thenCompose(name -> getUserInfoByName(name));
System.out.println("最终结果:" + finalFuture.get());
}
}
输出结果:
获取用户名,线程:pool-1-thread-1 获取用户信息,线程:pool-2-thread-1 最终结果:用户 1 - 年龄 20
2. 并行组合:thenCombine(合并两个任务的结果)
当两个任务并行执行,都完成后需要合并结果时,用 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;
}, Executors.newSingleThreadExecutor());
}
private static CompletableFuture<Double> getDiscount() {
return CompletableFuture.supplyAsync(() -> {
System.out.println("获取折扣,线程:" + Thread.currentThread().getName());
return 0.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
3. 批量任务:allOf / anyOf(等待所有或任意任务完成)
- allOf:等待所有异步任务完成(适合批量操作,比如同步多个数据源后统一处理);
- anyOf:等待任意一个异步任务完成(适合'最快响应'场景,比如调用多个镜像接口取最快的结果)。
代码示例:
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executors;
public class CompletableFutureDemo {
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));
}
public static void main(String[] args) throws Exception {
CompletableFuture<Void> allFuture = CompletableFuture.allOf(task(1), task(2), task(3));
allFuture.get();
System.out.println("===== 所有任务都完成了 =====");
CompletableFuture<Object> anyFuture = CompletableFuture.anyOf(task(1), task(2), task(3));
System.out.println("第一个完成的任务结果:" + anyFuture.get());
}
}
输出结果:
四、关键:别让异常悄悄溜走——异步任务的异常处理
异步任务的异常很'隐蔽'——如果不主动处理,它不会像同步代码那样直接抛出,而是悄悄消失。CompletableFuture 提供了三种核心异常处理方式,覆盖不同场景:
1. exceptionally:异常时返回兜底值
当任务抛出异常时,返回一个默认值(比如接口调用失败时返回空列表)。
示例场景:
模拟'任务执行时抛出除以零异常',用 exceptionally 返回兜底值。
2. handle:处理所有情况(正常/异常)并返回结果
无论任务是正常完成还是抛出异常,都能统一处理并返回新结果(比如正常时返回结果,异常时返回错误信息)。
3. whenComplete:消费结果或异常(无返回值)
无论任务是否异常,都能消费结果或异常信息(比如打印日志),但不返回新结果。
综合代码示例:
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executors;
public class CompletableFutureDemo {
public static void main(String[] args) throws Exception {
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
System.out.println("执行异步任务");
int a = 1 / 0;
return "正常结果";
}, Executors.newSingleThreadExecutor());
CompletableFuture<String> exceptionFuture = future.exceptionally(e -> {
System.out.println("捕获异常:" + e.getMessage());
return "异常兜底值";
});
CompletableFuture<String> handleFuture = exceptionFuture.handle((result, e) -> {
if (e != null) {
return "handle 处理异常:" + e.getMessage();
} else {
return "handle 处理正常:" + result;
}
});
CompletableFuture<Void> whenCompleteFuture = handleFuture.whenComplete((result, e) -> {
System.out.println("whenComplete 最终结果:" + result);
});
whenCompleteFuture.get();
}
}
输出结果:
执行异步任务 捕获异常:/ by zero whenComplete 最终结果:handle 处理异常:/ by zero
五、细节:get() vs join()——该用哪个获取结果?
获取 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(() -> "结果");
try {
String result1 = future.get();
System.out.println("get 获取结果:" + result1);
} catch (Exception e) {
e.printStackTrace();
}
String result2 = future.join();
System.out.println("join 获取结果:" + result2);
}
}
输出结果:
六、总结:CompletableFuture 核心用法速查表
通过本文的讲解,CompletableFuture 的常用能力可以归纳为 5 个核心点,直接对照使用即可:
- 创建任务:runAsync(无返回值)、supplyAsync(有返回值),必用自定义线程池;
- 结果处理:thenApply(加工结果)、thenAccept(消费结果)、thenRun(收尾操作),实现链式调用;
- 多任务组合:thenCompose(串行依赖)、thenCombine(并行合并)、allOf/anyOf(批量任务);
- 异常处理:exceptionally(兜底返回)、handle(处理并返回)、whenComplete(消费结果/异常);
- 结果获取:join()(非检查异常)比 get()(检查异常)更适合 Lambda 表达式。
最后:写在实战前的建议
- 优先使用自定义线程池:避免默认线程池耗尽,建议根据业务场景选择 FixedThreadPool(固定大小)或 CachedThreadPool(缓存线程);
- 避免过度链式调用:链式调用虽好,但过长的链会降低可读性,建议拆分逻辑为多个方法;
- 必处理异常:异步任务的异常不会主动抛出,务必用 exceptionally/handle/whenComplete 兜底。
掌握 CompletableFuture,不是为了'炫技',而是为了写出更高效、可维护的代码。
相关免费在线工具
- Keycode 信息
查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online
- Escape 与 Native 编解码
JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online
- JavaScript / HTML 格式化
使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online
- JavaScript 压缩与混淆
Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online
- Base64 字符串编码/解码
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
- Base64 文件转换器
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online