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 解决的是"怎么让一个方法异步执行"。
三、使用场景、优势、劣势
| 维度 | Future | CompletableFuture | @Async |
|---|---|---|---|
| 优势 | 简单直接,适合单任务异步 | 链式编排、非阻塞回调、任务组合、异常声明式处理 | 代码侵入最小,加注解即可 |
| 劣势 | get()阻塞主线程、无回调、无法组合任务 | 不指定线程池时默认用ForkJoinPool.commonPool()易线程饥饿 | 默认线程池不复用线程、同类调用失效、异常被吞 |
| 适用场景 | 简单异步任务,只需要拿结果 | 复杂任务编排(多任务并行/串行/组合) | 简单后台任务(日志、邮件、短信) |
回调的优势(CompletableFuture 独有)
- 非阻塞:注册回调后主线程立即返回,不占用线程资源
- 链式调用:thenApply → thenAccept → thenRun,流水线式编排
- 任务组合:thenCombine(合并)、allOf(等待所有)、anyOf(竞速)
- 异常声明式处理:exceptionally/handle 在回调链中统一兜底
- 手动完成:complete() 主动设置结果,支持超时兜底、事件驱动
四、举例场景(含耗时对比)
场景1:三个任务并行执行(分别耗时1s、2s、3s)
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(); // 几乎不阻塞
get() 卡住3秒。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)
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
1 CompletableFuture.supplyAsync(() -> taskA()) 2 .thenApply(a -> taskB(a)) 3 .thenApply(b -> taskC(b)) 4 .thenAccept(c -> sendResponse(c));
场景3:超时控制
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:竞速模式(哪个先完成用哪个)
isDone())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 的异常处理
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.supplyAsync(() -> task(), pool); // 内部实际执行: pool.execute(new AsyncSupply<>(pool, () -> task(), this));
1 CompletableFuture.supplyAsync(() -> task(), pool); 2 // 内部实际执行: 3 pool.execute(new AsyncSupply<>(pool, () -> task(), this));
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早已完成)
4. CompletableFuture 和 Future 不只是差一个回调
| 维度 | Future | CompletableFuture |
|---|---|---|
| 任务组合 | ❌ 无 | ✅ 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(); // 阻塞等待
浙公网安备 33010602011771号