【JDK8新特性】CompletableFuture异步编程Day10


写在前面

大家好,欢迎来到JDK8新特性系列教程的第10天!在前面的学习中,我们已经掌握了Lambda表达式、Stream API、Optional、接口默认方法、重复注解等核心特性。今天,我们将学习JDK8中最强大的并发工具之一------CompletableFuture

在JDK5引入的Future接口虽然提供了基本的异步任务支持,但它存在明显局限:无法方便地获取结果、不能链式组合多个异步任务、异常处理困难。CompletableFuture彻底解决了这些问题,提供了函数式、声明式的异步编程模型,让复杂的异步流程变得清晰优雅。

无论你是处理高并发Web请求、构建响应式系统,还是优化批处理任务,CompletableFuture都是必备技能。让我们开始今天的学习!


目录

一、异步编程的意义

1.1 为什么需要异步编程

在现代软件开发中,异步编程已经成为提升系统性能的关键技术:

复制代码
同步调用:
[请求1]=====[等待IO]=====[处理结果]  
          [请求2]=====[等待IO]=====[处理结果]
                    [请求3]=====[等待IO]=====[处理结果]
                    
异步调用:
[请求1]--发起-->[等待IO]  
[请求2]--发起-->[等待IO]  --回调-->[处理结果1,2,3]
[请求3]--发起-->[等待IO]

1.2 异步编程的优势

优势 说明 典型场景
提高吞吐量 不阻塞线程,一个线程处理多个请求 Web服务器
降低延迟 并行执行多个独立任务 聚合多个服务数据
资源高效 减少线程创建,降低上下文切换 高并发系统
响应性 UI线程不被阻塞,保持界面流畅 桌面/移动应用

1.3 实际场景举例

场景1:电商订单查询

java 复制代码
// 同步方式:串行执行,总耗时 = 100 + 80 + 50 = 230ms
public OrderDetail getOrderDetail(String orderId) {
    Order order = orderService.getOrder(orderId);           // 100ms
    User user = userService.getUser(order.getUserId());     // 80ms
    List<Item> items = itemService.getItems(orderId);       // 50ms
    return new OrderDetail(order, user, items);
}

// 异步方式:并行执行,总耗时 ≈ max(100, 80, 50) = 100ms
public CompletableFuture<OrderDetail> getOrderDetailAsync(String orderId) {
    CompletableFuture<Order> orderFuture = CompletableFuture
        .supplyAsync(() -> orderService.getOrder(orderId));
    CompletableFuture<User> userFuture = CompletableFuture
        .supplyAsync(() -> userService.getUser(orderId));
    CompletableFuture<List<Item>> itemsFuture = CompletableFuture
        .supplyAsync(() -> itemService.getItems(orderId));
    
    return CompletableFuture.allOf(orderFuture, userFuture, itemsFuture)
        .thenApply(v -> new OrderDetail(
            orderFuture.join(),
            userFuture.join(),
            itemsFuture.join()
        ));
}

二、Future接口的局限性

2.1 Future的基本用法

JDK5引入的Future接口提供了基本的异步支持:

java 复制代码
ExecutorService executor = Executors.newFixedThreadPool(4);

// 提交异步任务
Future<String> future = executor.submit(() -> {
    Thread.sleep(1000);
    return "Hello";
});

// 获取结果(阻塞!)
String result = future.get();  // 阻塞直到结果返回
System.out.println(result);

executor.shutdown();

2.2 Future的局限性

局限性 说明 影响
get()阻塞 获取结果时阻塞当前线程 失去异步意义
无法链式调用 不能方便地组合多个Future 代码复杂
异常处理困难 异常在get()时才抛出 难以定位问题
无法手动完成 不能从外部设置结果 灵活性差
没有回调机制 不能设置完成后的操作 需要轮询

2.3 复杂场景的困境

假设需要实现:获取用户 -> 获取订单 -> 计算总价

java 复制代码
// 使用Future的丑陋实现
Future<User> userFuture = executor.submit(() -> getUser(userId));
User user = userFuture.get();  // 阻塞

Future<List<Order>> ordersFuture = executor.submit(() -> getOrders(user.getId()));
List<Order> orders = ordersFuture.get();  // 阻塞

Future<BigDecimal> totalFuture = executor.submit(() -> calculateTotal(orders));
BigDecimal total = totalFuture.get();  // 阻塞

// 代码嵌套深、异常处理复杂、难以维护

三、CompletableFuture的创建

3.1 基本创建方式

CompletableFuture提供了多种创建方式:

java 复制代码
// 方式1:runAsync - 无返回值
CompletableFuture<Void> future1 = CompletableFuture.runAsync(() -> {
    System.out.println("异步任务,无返回值");
});

// 方式2:supplyAsync - 有返回值
CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> {
    return "Hello, CompletableFuture!";
});

// 方式3:completedFuture - 已完成
CompletableFuture<String> future3 = CompletableFuture.completedFuture("已完成");

// 方式4:failedFuture - 已失败(JDK9+)
CompletableFuture<String> future4 = CompletableFuture.failedFuture(
    new RuntimeException("失败")
);

3.2 使用自定义线程池

java 复制代码
// 创建自定义线程池
ExecutorService executor = new ThreadPoolExecutor(
    4,                      // 核心线程数
    8,                      // 最大线程数
    60,                     // 空闲线程存活时间
    TimeUnit.SECONDS,       // 时间单位
    new LinkedBlockingQueue<>(100),  // 任务队列
    new ThreadFactory() {   // 线程工厂
        private final AtomicInteger count = new AtomicInteger(0);
        @Override
        public Thread newThread(Runnable r) {
            return new Thread(r, "custom-pool-" + count.incrementAndGet());
        }
    },
    new ThreadPoolExecutor.CallerRunsPolicy()  // 拒绝策略
);

// 使用自定义线程池
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
    System.out.println("执行线程: " + Thread.currentThread().getName());
    return "使用自定义线程池";
}, executor);

3.3 手动控制完成

java 复制代码
CompletableFuture<String> future = new CompletableFuture<>();

// 在其他线程中完成
new Thread(() -> {
    try {
        Thread.sleep(1000);
        future.complete("任务完成");  // 正常完成
        // future.completeExceptionally(new RuntimeException("失败"));  // 异常完成
    } catch (InterruptedException e) {
        future.completeExceptionally(e);
    }
}).start();

// 取消任务
// future.cancel(true);

String result = future.get();
System.out.println(result);

四、异步回调

4.1 转换结果:thenApply

java 复制代码
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> "Hello");

// thenApply: 接收上一步结果,转换后返回
CompletableFuture<String> result = future
    .thenApply(s -> s + " World")
    .thenApply(String::toUpperCase);

System.out.println(result.join());  // HELLO WORLD

4.2 消费结果:thenAccept

java 复制代码
CompletableFuture.supplyAsync(() -> "Hello")
    .thenApply(s -> s + " World")
    .thenAccept(System.out::println);  // Hello World,无返回值

4.3 纯副作用:thenRun

java 复制代码
CompletableFuture.supplyAsync(() -> "Hello")
    .thenRun(() -> System.out.println("任务完成,不依赖结果"));

4.4 回调方法对比

方法 输入 输出 用途
thenApply 上一步结果 转换后的结果 数据转换
thenAccept 上一步结果 Void 消费结果
thenRun Void 执行副作用

4.5 异步版本

每个回调方法都有对应的异步版本(带Async后缀):

java 复制代码
ExecutorService executor = Executors.newFixedThreadPool(4);

CompletableFuture.supplyAsync(() -> {
    System.out.println("supplyAsync: " + Thread.currentThread().getName());
    return "Hello";
}, executor)
.thenApply(s -> {
    System.out.println("thenApply: " + Thread.currentThread().getName());
    return s + " World";
})
.thenApplyAsync(s -> {  // 使用默认线程池
    System.out.println("thenApplyAsync: " + Thread.currentThread().getName());
    return s.toUpperCase();
})
.thenApplyAsync(s -> {  // 使用自定义线程池
    System.out.println("thenApplyAsync(custom): " + Thread.currentThread().getName());
    return s + "!";
}, executor)
.thenAccept(System.out::println);

4.6 异常处理

java 复制代码
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
    if (true) {
        throw new RuntimeException("出错了!");
    }
    return "Hello";
});

// exceptionally: 捕获异常,提供默认值
CompletableFuture<String> withFallback = future
    .exceptionally(ex -> {
        System.out.println("异常: " + ex.getMessage());
        return "默认值";
    });

System.out.println(withFallback.join());  // 默认值

// whenComplete: 无论成功失败都执行
CompletableFuture<String> withLog = future
    .whenComplete((result, ex) -> {
        if (ex != null) {
            System.out.println("执行失败: " + ex.getMessage());
        } else {
            System.out.println("执行成功: " + result);
        }
    });

// handle: 统一处理成功和失败
CompletableFuture<String> handled = future
    .handle((result, ex) -> {
        if (ex != null) {
            return "错误: " + ex.getMessage();
        }
        return result;
    });

4.7 异常处理方法对比

方法 成功时 失败时 返回值
exceptionally 原样返回 执行回调,提供默认值 CompletableFuture
whenComplete 执行回调 执行回调 CompletableFuture(不改变结果)
handle 执行回调 执行回调 CompletableFuture++(可转换类型)++

五、异步组合

5.1 串行组合:thenCompose

当一个异步操作的结果作为下一个异步操作的输入时使用:

java 复制代码
// 场景:先获取用户,再根据用户获取订单
public CompletableFuture<User> getUserAsync(String userId) {
    return CompletableFuture.supplyAsync(() -> userService.getUser(userId));
}

public CompletableFuture<List<Order>> getOrdersAsync(String userId) {
    return CompletableFuture.supplyAsync(() -> orderService.getOrders(userId));
}

// 使用thenCompose串行执行
CompletableFuture<List<Order>> ordersFuture = getUserAsync("user123")
    .thenCompose(user -> getOrdersAsync(user.getId()));

// 对比:使用thenApply会得到嵌套的CompletableFuture
CompletableFuture<CompletableFuture<List<Order>>> nested = getUserAsync("user123")
    .thenApply(user -> getOrdersAsync(user.getId()));  // 不要这样用!

5.2 并行组合:thenCombine

当两个异步操作独立执行,需要合并结果时使用:

java 复制代码
CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> {
    sleep(100);
    return "Hello";
});

CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> {
    sleep(200);
    return "World";
});

// thenCombine: 等待两个都完成,合并结果
CompletableFuture<String> combined = future1
    .thenCombine(future2, (s1, s2) -> s1 + " " + s2);

System.out.println(combined.join());  // Hello World

5.3 等待多个任务:allOf

java 复制代码
CompletableFuture<String> task1 = CompletableFuture.supplyAsync(() -> {
    sleep(100);
    return "任务1";
});

CompletableFuture<String> task2 = CompletableFuture.supplyAsync(() -> {
    sleep(200);
    return "任务2";
});

CompletableFuture<String> task3 = CompletableFuture.supplyAsync(() -> {
    sleep(150);
    return "任务3";
});

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

// 等待所有任务完成
all.join();

// 获取各个结果
System.out.println(task1.join());  // 任务1
System.out.println(task2.join());  // 任务2
System.out.println(task3.join());  // 任务3

5.4 获取最快结果:anyOf

java 复制代码
CompletableFuture<String> task1 = CompletableFuture.supplyAsync(() -> {
    sleep(300);
    return "来自服务A";
});

CompletableFuture<String> task2 = CompletableFuture.supplyAsync(() -> {
    sleep(100);
    return "来自服务B";
});

CompletableFuture<String> task3 = CompletableFuture.supplyAsync(() -> {
    sleep(200);
    return "来自服务C";
});

// anyOf: 返回最先完成的
CompletableFuture<Object> any = CompletableFuture.anyOf(task1, task2, task3);

System.out.println(any.join());  // 来自服务B(最快)

5.5 组合方法对比

方法 场景 特点
thenCompose 串行依赖 扁平化嵌套Future
thenCombine 并行+合并 两个任务独立执行
allOf 等待所有 返回Void,需单独获取结果
anyOf 获取最快 返回Object,需类型转换

六、异常处理和超时控制

6.1 完整的异常处理策略

java 复制代码
public CompletableFuture<String> robustAsyncOperation() {
    return CompletableFuture.supplyAsync(() -> {
        // 可能抛出异常的操作
        return riskyOperation();
    })
    .thenApply(result -> {
        // 转换结果,可能抛出异常
        return transform(result);
    })
    .exceptionally(ex -> {
        // 捕获所有异常,提供默认值
        log.error("操作失败", ex);
        return "默认值";
    })
    .whenComplete((result, ex) -> {
        // 记录日志,无论成功失败
        log.info("操作完成,结果: {}", result);
    });
}

6.2 JDK9+的超时控制

java 复制代码
// JDK9+ 提供了orTimeout和completeOnTimeout
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
    sleep(5000);  // 耗时5秒
    return "结果";
})
.orTimeout(3, TimeUnit.SECONDS)  // 3秒超时抛出TimeoutException
.exceptionally(ex -> "超时默认值");

// completeOnTimeout: 超时提供默认值
CompletableFuture<String> withTimeout = CompletableFuture.supplyAsync(() -> {
    sleep(5000);
    return "结果";
})
.completeOnTimeout("默认值", 3, TimeUnit.SECONDS);

6.3 JDK8的超时实现

java 复制代码
public static <T> CompletableFuture<T> withTimeout(
        CompletableFuture<T> future, 
        long timeout, 
        TimeUnit unit) {
    
    CompletableFuture<T> timeoutFuture = new CompletableFuture<>();
    
    // 延迟任务,超时后完成
    Delayer.delay(() -> timeoutFuture.completeExceptionally(
        new TimeoutException("操作超时")
    ), timeout, unit);
    
    // 返回先完成的那个
    return (CompletableFuture<T>) CompletableFuture.anyOf(future, timeoutFuture);
}

// 使用
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
    sleep(5000);
    return "结果";
});

CompletableFuture<String> withTimeout = withTimeout(future, 3, TimeUnit.SECONDS)
    .exceptionally(ex -> "超时");

七、踩坑提醒与经验之谈

7.1 坑点一:默认线程池是ForkJoinPool.commonPool

java 复制代码
// 危险!使用默认线程池
CompletableFuture.supplyAsync(() -> {
    // 默认使用ForkJoinPool.commonPool()
    // 线程数 = CPU核心数 - 1
    return doSomething();
});

// 问题:
// 1. 线程数有限,可能耗尽
// 2. 阻塞操作会占用线程,影响其他任务
// 3. 无法自定义线程名、拒绝策略等

经验 :生产环境始终使用自定义线程池

java 复制代码
private static final ExecutorService executor = new ThreadPoolExecutor(
    4, 8, 60, TimeUnit.SECONDS,
    new LinkedBlockingQueue<>(1000),
    new ThreadFactoryBuilder().setNameFormat("async-pool-%d").build(),
    new ThreadPoolExecutor.CallerRunsPolicy()
);

// 使用自定义线程池
CompletableFuture.supplyAsync(() -> doSomething(), executor);

7.2 坑点二:注意线程池隔离

java 复制代码
// 错误:CPU密集型任务和IO密集型任务混用线程池
ExecutorService mixedPool = Executors.newFixedThreadPool(10);

// CPU密集型任务
CompletableFuture.supplyAsync(() -> heavyComputation(), mixedPool);

// IO密集型任务
CompletableFuture.supplyAsync(() -> callRemoteService(), mixedPool);

经验:根据任务类型使用不同的线程池

java 复制代码
// CPU密集型:线程数 = CPU核心数
ExecutorService cpuPool = Executors.newFixedThreadPool(
    Runtime.getRuntime().availableProcessors()
);

// IO密集型:线程数可以更多
ExecutorService ioPool = new ThreadPoolExecutor(
    10, 50, 60, TimeUnit.SECONDS,
    new LinkedBlockingQueue<>(1000)
);

7.3 坑点三:异常被吞掉

java 复制代码
// 危险:异常被吞掉,无法感知
CompletableFuture.supplyAsync(() -> {
    throw new RuntimeException("出错了");
});
// 没有后续处理,异常被忽略!

// 正确:必须处理异常
CompletableFuture.supplyAsync(() -> {
    throw new RuntimeException("出错了");
})
.exceptionally(ex -> {
    log.error("异常", ex);
    return "默认值";
});

// 或者等待获取结果(会抛出异常)
try {
    future.get();
} catch (Exception e) {
    log.error("异常", e);
}

7.4 坑点四:join() vs get()

java 复制代码
// get():抛出受检异常,需要try-catch
try {
    String result = future.get();
} catch (InterruptedException | ExecutionException e) {
    e.printStackTrace();
}

// join():抛出非受检异常,代码更简洁
String result = future.join();

// 但要注意:join()会包装异常
// ExecutionException -> CompletionException

7.5 经验之谈

  1. 始终使用自定义线程池:默认的common pool不适合生产环境
  2. 注意异常处理:避免异常被静默吞掉
  3. 合理使用方法thenApply用于转换,thenCompose用于链式,thenCombine用于合并
  4. 避免阻塞操作 :在CompletableFuture链中避免使用get()/join()阻塞
  5. 资源释放:应用关闭时记得关闭线程池

八、面试高频考点

考点一:CompletableFuture和Future的区别?

特性 Future CompletableFuture
获取结果 get()阻塞 支持回调,非阻塞
链式调用 不支持 支持thenApply等链式操作
组合能力 支持thenCombine、allOf等
异常处理 困难 exceptionally、handle等
手动完成 不支持 支持complete()
编程模型 命令式 函数式、声明式

考点二:如何处理异步异常?

:CompletableFuture提供了三种异常处理方式:

  1. exceptionally:捕获异常,提供默认值

    java 复制代码
    future.exceptionally(ex -> "默认值");
  2. whenComplete:无论成功失败都执行,不改变结果

    java 复制代码
    future.whenComplete((result, ex) -> { /* 记录日志 */ });
  3. handle:统一处理成功和失败,可以转换结果类型

    java 复制代码
    future.handle((result, ex) -> ex != null ? "错误" : result);

考点三:thenApply和thenCompose的区别?

  • thenApply:用于转换结果,返回普通值

    java 复制代码
    future.thenApply(s -> s.toUpperCase())  // CompletableFuture<String>
  • thenCompose:用于链式调用,返回另一个CompletableFuture(扁平化)

    java 复制代码
    future.thenCompose(s -> asyncOperation(s))  // CompletableFuture<String>

如果用thenApply返回CompletableFuture,会得到嵌套的CompletableFuture<CompletableFuture<T>>

考点四:allOf和anyOf的区别?

  • allOf :等待所有任务完成,返回CompletableFuture<Void>
  • anyOf :返回最先完成的任务,返回CompletableFuture<Object>

注意:allOf返回的是Void,需要单独通过各个Future获取结果;anyOf返回的是Object,需要类型转换。

考点五:CompletableFuture的默认线程池是什么?有什么问题?

:默认使用ForkJoinPool.commonPool(),线程数等于CPU核心数 - 1

问题

  1. 线程数有限,容易耗尽
  2. 阻塞操作会占用线程,影响其他任务
  3. 无法自定义线程名、拒绝策略

建议:生产环境始终使用自定义线程池。


九、总结

今天我们深入学习了CompletableFuture异步编程:

  1. 异步编程意义:提高吞吐量、降低延迟、资源高效利用
  2. Future的局限:get阻塞、不能链式、异常处理困难
  3. 创建方式:runAsync、supplyAsync、completedFuture
  4. 异步回调:thenApply、thenAccept、thenRun及其Async版本
  5. 异常处理:exceptionally、whenComplete、handle
  6. 异步组合:thenCompose串行、thenCombine并行、allOf等待全部、anyOf获取最快
  7. 超时控制:JDK9+的orTimeout/completeOnTimeout,JDK8的自定义实现
  8. 踩坑提醒:默认线程池问题、线程池隔离、异常处理

下一步预告

Day11:新工具类

JDK8还带来了许多实用的新工具类:

  • Objects工具类的空安全操作
  • StringJoiner和String.join
  • Base64编码解码
  • 新的日期时间API(LocalDate、LocalTime、LocalDateTime)
  • Optional的更多用法

敬请期待!


参考资料

  1. Oracle官方文档:CompletableFuture
  2. Java 8 CompletableFuture Tutorial

互动话题

  1. 你在项目中使用过CompletableFuture吗? 是用来解决什么场景的?欢迎在评论区分享你的使用经验。

  2. 思考题:假设你需要同时调用3个外部API获取数据,只要任意2个返回就可以继续处理,你会如何用CompletableFuture实现?

  3. 踩坑分享:你在使用CompletableFuture时遇到过什么坑?有没有被默认线程池"坑"过的经历?


如果这篇文章对你有帮助,请点赞、收藏、转发支持一下!关注专栏,持续学习JDK8新特性!

Day10打卡:CompletableFuture异步编程 ✅

相关推荐
秋922 分钟前
Go语言(Golang)开发工程师全景解析:岗位职责·语言优势与使用场景·各城市薪资·发展前景·高考志愿填报(2026版)
开发语言·golang·高考
IT_陈寒40 分钟前
Redis持久化这个坑,我爬了一整天才出来
前端·人工智能·后端
无风听海40 分钟前
多租户系统中的 OIDC:Discovery 端点与联合登录的深度实践
后端·python·flask
小小前端仔LC1 小时前
Node.js + LangChain + React:搭建个人知识库(六)- “吃什么”项目实战:从700+菜谱入库到Taro H5端JSON渲染
前端·后端
huangdong_1 小时前
1688商品图片采集技术解析:登录态处理与SKU图自动分类
开发语言
马士兵教育1 小时前
Java还有前景吗?Java+AI大模型学习路线及项目?
java·人工智能·python·学习·机器学习
chase_my_dream1 小时前
C++ + SLAM 高频面试问题整理
开发语言·c++·面试
程序员黑豆2 小时前
AI全栈开发之Java:怎么配置Java环境变量
前端·后端·ai编程
snow@li2 小时前
Java:理解 Gradle / 后端项目的管家 / 打包SpringBoot 应用 / 完成编译、下载依赖、运行测试、打包 JAR/WAR / 速查表
java
Cloud_Shy6182 小时前
解读《Effective Python 3rd Edition》:从练气到老魔(第五章 Item 30 - 32)
开发语言·人工智能·笔记·python·学习方法