Java基础学习笔记--CompletableFuture、Future、@Async

一、三者的介绍

工具引入版本本质定位一句话概括
Future JDK 5 异步结果的占位符 "我提交了一个任务,未来某个时刻能拿到结果"
CompletableFuture JDK 8 异步任务的编排器 "我提交了一组任务,它们完成后自动触发后续逻辑"
@Async Spring 3.x 声明式异步注解 "加个注解,这个方法就异步执行"

核心比喻:线程池是发动机,@Async 是自动挡,CompletableFuture 是手动挡+变速箱。

二、使用方式的不同、区别和联系

1. Future(命令式)

ExecutorService pool = Executors.newFixedThreadPool(4); Future<String> result = pool.submit(() -> queryDB()); String data = result.get(); // 阻塞等待

ExecutorService pool = Executors.newFixedThreadPool(4);
Future<String> result = pool.submit(() -> queryDB());
String data = result.get(); // 阻塞等待

2. CompletableFuture(函数式链式调用)

1 CompletableFuture<User> userFuture = CompletableFuture.supplyAsync(() -> queryUser(), pool);
3 CompletableFuture<Order> orderFuture = CompletableFuture.supplyAsync(() -> queryOrder(), pool);
5 
6 userFuture.thenCombine(orderFuture, (user, order) -> {
7     return buildResponse(user, order);
8 }).thenAccept(response -> sendToClient(response));

3. @Async(声明式)

1 @Service
2 public class OrderService {
3     @Async("asyncExecutor")
4     public void sendSms(String phone) {
5         // 发送短信逻辑
6     }
7 }

4. 三者关系图

┌─────────────────────────────────────────────────────────┐ │ 线程池(Executor) │ │ 所有异步任务的底层执行引擎 │ └─────────────────────────────────────────────────────────┘ ▲ ▲ │ │ ┌────────┴────────┐ ┌──────┴──────────┐ │ @Async │ │ CompletableFuture │ │ (Spring框架级) │ │ (JDK工具级) │ └─────────────────┘ └──────────────────┘

┌─────────────────────────────────────────────────────────┐
│                    线程池(Executor)                      │
│              所有异步任务的底层执行引擎                      │
└─────────────────────────────────────────────────────────┘
              ▲                    ▲
              │                    │
     ┌────────┴────────┐   ┌──────┴──────────┐
     │   @Async        │   │  CompletableFuture │
     │  (Spring框架级)  │   │   (JDK工具级)     │
     └─────────────────┘   └──────────────────┘

核心区别:Future 解决的是"怎么拿到异步结果",CompletableFuture 解决的是"怎么编排一组异步任务",@Async 解决的是"怎么让一个方法异步执行"。

三、使用场景、优势、劣势

维度FutureCompletableFuture@Async
优势 简单直接,适合单任务异步 链式编排、非阻塞回调、任务组合、异常声明式处理 代码侵入最小,加注解即可
劣势 get()阻塞主线程、无回调、无法组合任务 不指定线程池时默认用ForkJoinPool.commonPool()易线程饥饿 默认线程池不复用线程、同类调用失效、异常被吞
适用场景 简单异步任务,只需要拿结果 复杂任务编排(多任务并行/串行/组合) 简单后台任务(日志、邮件、短信)

回调的优势(CompletableFuture 独有)

  1. 非阻塞:注册回调后主线程立即返回,不占用线程资源
  2. 链式调用:thenApply → thenAccept → thenRun,流水线式编排
  3. 任务组合:thenCombine(合并)、allOf(等待所有)、anyOf(竞速)
  4. 异常声明式处理:exceptionally/handle 在回调链中统一兜底
  5. 手动完成:complete() 主动设置结果,支持超时兜底、事件驱动

四、举例场景(含耗时对比)

场景1:三个任务并行执行(分别耗时1s、2s、3s)

Future 方式:
1 Future<String> f1 = pool.submit(() -> task1()); // 1s
2 Future<String> f2 = pool.submit(() -> task2()); // 2s
3 Future<String> f3 = pool.submit(() -> task3()); // 3s
4 
5 String r1 = f1.get();  // 阻塞约3s(等最慢的f3)
6 String r2 = f2.get();  // 几乎不阻塞
7 String r3 = f3.get();  // 几乎不阻塞
总耗时 ≈ 3s,但主线程被 get() 卡住3秒。
CompletableFuture 方式:

CompletableFuture<String> f1 = CompletableFuture.supplyAsync(() -> task1(), pool); CompletableFuture<String> f2 = CompletableFuture.supplyAsync(() -> task2(), pool); CompletableFuture<String> f3 = CompletableFuture.supplyAsync(() -> task3(), pool); f1.thenCombine(f2, (r1, r2) -> r1 + r2) .thenCombine(f3, (r12, r3) -> r12 + r3) .thenAccept(result -> sendResponse(result));

1 CompletableFuture<String> f1 = CompletableFuture.supplyAsync(() -> task1(), pool);
2 CompletableFuture<String> f2 = CompletableFuture.supplyAsync(() -> task2(), pool);
3 CompletableFuture<String> f3 = CompletableFuture.supplyAsync(() -> task3(), pool);
4 
5 f1.thenCombine(f2, (r1, r2) -> r1 + r2)
6   .thenCombine(f3, (r12, r3) -> r12 + r3)
7   .thenAccept(result -> sendResponse(result));

场景2:串行依赖(A→B→C,每个1s)

Future 方式:
1 Future<String> fa = pool.submit(() -> taskA());
2 String ra = fa.get(); // 阻塞1s
3 Future<String> fb = pool.submit(() -> taskB(ra));
4 String rb = fb.get(); // 阻塞1s
5 Future<String> fc = pool.submit(() -> taskC(rb));
6 String rc = fc.get(); // 阻塞1s
总耗时 = 3s,主线程全程阻塞。
CompletableFuture 方式:
1 CompletableFuture.supplyAsync(() -> taskA())
2     .thenApply(a -> taskB(a))
3     .thenApply(b -> taskC(b))
4     .thenAccept(c -> sendResponse(c));
总耗时 = 3s,主线程不阻塞。

场景3:超时控制

Future 方式:
1 Future<String> f = pool.submit(() -> task());
2 try {
3     String result = f.get(5, TimeUnit.SECONDS); // 超时抛异常
4 } catch (TimeoutException e) {
5     // 处理超时
6 }
1 Future<String> f = pool.submit(() -> task());
2 try {
3     String result = f.get(5, TimeUnit.SECONDS); // 超时抛异常
4 } catch (TimeoutException e) {
5     // 处理超时
6 }

CompletableFuture 方式:

1 CompletableFuture.supplyAsync(() -> task())
2     .orTimeout(5, TimeUnit.SECONDS)           // 超时抛异常
3     .exceptionally(ex -> defaultValue);       // 超时兜底

场景4:竞速模式(哪个先完成用哪个)

Future 方式:无法实现(需要手动轮询 isDone())
CompletableFuture 方式:
1 CompletableFuture.anyOf(f1, f2, f3).thenAccept(result -> useFastest(result));

五、系统默认线程池

1. CompletableFuture 的默认线程池

1 // 不指定线程池时,默认使用 ForkJoinPool.commonPool()
2 CompletableFuture.supplyAsync(() -> task()); // 使用 commonPool
特点:
  • 整个 JVM 共享一个 commonPool
  • 线程数 = CPU核心数 - 1
  • 风险:多个 CompletableFuture 共用同一个池,容易线程饥饿
建议:始终指定自定义线程池
1 CompletableFuture.supplyAsync(() -> task(), customExecutor);

2. @Async 的默认线程池

1 // 不指定线程池时,默认使用 SimpleAsyncTaskExecutor
2 @Async
3 public void task() { }
特点:
  • 每次调用都新建线程,不复用线程
  • 高并发下直接 OOM
建议:配置自定义线程池
 1 @Configuration
 2 @EnableAsync
 3 public class AsyncConfig {
 4     @Bean("asyncExecutor")
 5     public ThreadPoolTaskExecutor asyncExecutor() {
 6         ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
 7         executor.setCorePoolSize(10);
 8         executor.setMaxPoolSize(50);
 9         executor.setQueueCapacity(1000);
10         executor.setThreadNamePrefix("async-");
11         executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
12         executor.initialize();
13         return executor;
14     }
15 }

六、异常处理

1. Future 的异常处理

 1 Future<String> f = pool.submit(() -> {
 2     if (true) throw new RuntimeException("任务失败");
 3     return "result";
 4 });
 5 
 6 try {
 7     String result = f.get();
 8 } catch (ExecutionException e) {
 9     // 异常被包装成 ExecutionException
10     Throwable cause = e.getCause(); // 获取原始异常
11     cause.printStackTrace();
12 }
问题:异常只能在 get() 时捕获,容易丢失原始异常信息。

2. CompletableFuture 的异常处理

1 CompletableFuture.supplyAsync(() -> queryDB())
2     .exceptionally(ex -> {
3         log.error("查询失败", ex);
4         return defaultValue;  // 优雅兜底
5     })
6     .handle((result, ex) -> {
7         // 无论成功失败都会执行
8         return ex != null ? fallback : result;
9     });
优势:异常沿回调链自动传播,可在任意位置统一处理。

3. @Async 的异常处理

void 方法:异常默认被吞掉,需配置全局处理器
 1 @Configuration
 2 @EnableAsync
 3 public class AsyncConfig implements AsyncConfigurer {
 4     @Override
 5     public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
 6         return (ex, method, params) -> {
 7             log.error("异步方法异常: {}", method.getName(), ex);
 8         };
 9     }
10 }

返回 CompletableFuture 的方法:异常在 join() 或 get() 时抛出

1 @Async
2 public CompletableFuture<String> task() {
3     return CompletableFuture.supplyAsync(() -> {
4         throw new RuntimeException("失败");
5     });
6 }
7 // 调用方
8 task().join(); // 抛出 CompletionException

七、其他

1. CompletableFuture 的本质:编排而非执行

核心比喻:CompletableFuture 是包工头,线程池是施工队。包工头不干活,只负责分配任务和协调流程。
源码还原:

CompletableFuture.supplyAsync(() -> task(), pool); // 内部实际执行: pool.execute(new AsyncSupply<>(pool, () -> task(), this));

1 CompletableFuture.supplyAsync(() -> task(), pool);
2 // 内部实际执行:
3 pool.execute(new AsyncSupply<>(pool, () -> task(), this));
CompletableFuture 只是把任务包装成 AsyncSupply,交给线程池执行,自己不执行任何任务。

2. thenCombine 的底层真相

餐厅点餐比喻:
  • 你点了主食(f1)和饮料(f2)
  • 厨房并行制作(线程池并行执行)
  • 你不需要站在柜台等,而是说:"主食和饮料都好了,就帮我装袋"(thenCombine 注册回调)
  • 两样都好后,服务员自动装袋给你(回调自动触发)
两种等待对比:
等待方式谁在等是否阻塞
Future.get() 主线程亲自等 是,挂起主线程
CompletableFuture 回调 CompletableFuture 内部等 否,主线程立即返回

3. Future 开子线程的意义:并行加速

核心答案:虽然 get() 会阻塞,但子线程从提交那一刻起就在并行执行了。
1 // 三个任务各耗时1s
2 Future<String> f1 = pool.submit(() -> task1());
3 Future<String> f2 = pool.submit(() -> task2());
4 Future<String> f3 = pool.submit(() -> task3());
5 
6 String r1 = f1.get();  // 阻塞约1s
7 String r2 = f2.get();  // 几乎不阻塞(f2早已完成)
8 String r3 = f3.get();  // 几乎不阻塞(f3早已完成)
总耗时 ≈ 1s(最慢任务的耗时),而不是3s。这就是 Future 的核心价值:把串行任务变成并行,大幅缩短总耗时。

4. CompletableFuture 和 Future 不只是差一个回调

维度FutureCompletableFuture
任务组合 ❌ 无 ✅ allOf/anyOf/thenCombine
异常处理 ❌ 被动捕获 ✅ 声明式兜底
手动完成 ❌ 无 ✅ complete/completeExceptionally
超时控制 ⚠️ 仅 get(timeout) ✅ orTimeout/completeOnTimeout

5. 常见坑点汇总

坑点说明解决方案
CompletableFuture 不指定线程池 默认用 commonPool,易线程饥饿 始终指定自定义线程池
@Async 默认线程池 SimpleAsyncTaskExecutor 不复用线程 配置 ThreadPoolTaskExecutor
@Async 同类调用失效 this.asyncMethod() 不走代理 注入自身代理或拆分类
CompletableFuture 异常被吞 不用 exceptionally 则异常静默丢失 始终加 exceptionally/handle
Future.get() 阻塞 把异步变回同步 改用 CompletableFuture 回调

6. 选型建议口诀

简单后台任务用 @Async,复杂任务编排用 CompletableFuture,两者都别忘了配自定义线程池。

ExecutorService pool = Executors.newFixedThreadPool(4); Future<String> result = pool.submit(() -> queryDB()); String data = result.get(); // 阻塞等待

ExecutorService pool = Executors.newFixedThreadPool(4); Future<String> result = pool.submit(() -> queryDB()); String data = result.get(); // 阻塞等待

posted @ 2026-09-21 22:16  倾听-静轩水月  阅读(10)  评论(0)    收藏  举报