CompletableFuture

CompletableFuture

CompletableFuture 是 Java 8 引入的一个强大的异步编程工具,它实现了 Future 和 CompletionStage 接口,提供了丰富的 API 来处理异步计算和组合多个异步操作。

如果不指定线程池,默认使用 ForkJoinPool,大量耗时短的操作可以充分利用 CPU 多核能力(分治、工作窃取)

主要特点

  1. 异步执行:可以在后台线程中执行任务
  2. 链式调用:可以串联多个异步操作
  3. 组合操作:可以组合多个 CompletableFuture
  4. 异常处理:提供了完善的异常处理机制
  5. 手动完成:可以手动设置完成状态和结果

常用方法

1. 创建 CompletableFuture

  • supplyAsync() 传入的是 Supplier,runAsync() 传入的是 Runnable,都是异步操作
  • 默认使用 ForkJoinPool.commonPool() 线程池,也可以指定自定义的线程池
// 提交一个任务(代码块作为任务)
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
    // TODO
    return "Hello";
});

// 提交一个任务(线程就是任务)
CompletableFuture<String> future = CompletableFuture.runAsync(() -> {
    // TODO
});

// 使用自定义线程池
ExecutorService executor = Executors.newFixedThreadPool(5);
CompletableFuture<String> futureWithExecutor = CompletableFuture.supplyAsync(() -> {
    // TODO
    return "Result";
}, executor);

2. 处理单个CompletableFuture结果

两类方法,区别是本次处理是否有返回值

thenApply 系列

方法 同步/异步 线程行为 返回值
thenApply(Function) 同步 沿用上一任务的线程 CompletableFuture<U>
thenApplyAsync(Function) 异步 默认 ForkJoinPool.commonPool() CompletableFuture<U>
thenApplyAsync(Function, Executor) 异步 使用指定线程池 CompletableFuture<U>

thenAccept 系列

方法 同步/异步 线程行为 返回值
thenAccept(Consumer) 同步 沿用上一任务的线程 CompletableFuture<Void>
thenAcceptAsync(Consumer) 异步 默认 ForkJoinPool.commonPool() CompletableFuture<Void>
thenAcceptAsync(Consumer, Executor) 异步 使用指定线程池 CompletableFuture<Void>

示例

// 有返回值
CompletableFuture<String> greetingFuture = future.thenApply(name -> {
    return name + " World!";
});

// 无返回值(返回值是null)
future.thenAccept(result -> {
    System.out.println("Received: " + result);
});

3. 处理多个 CompletableFuture

  1. 调度线程阻塞,直到多个 CompletableFuture 执行完成(信号量和计数器也能实现)

    // 创建三个异步任务
    CompletableFuture<String> task1 = CompletableFuture.supplyAsync(() -> {
        sleep(1000);
        return "任务1完成";
    });
    
    CompletableFuture<String> task2 = CompletableFuture.supplyAsync(() -> {
        sleep(1500);
        return "任务2完成";
    });
    
    CompletableFuture<String> task3 = CompletableFuture.supplyAsync(() -> {
        sleep(800);
        return "任务3完成";
    });
    
    // 使用allOf等待所有任务完成
    CompletableFuture<Void> allFutures = CompletableFuture.allOf(task1, task2, task3);
    
    // 阻塞直到所有任务完成
    allFutures.get();
    
    System.out.println("所有任务已完成:");
    System.out.println(task1.get());
    System.out.println(task2.get());
    System.out.println(task3.get());
    
  2. 两个 CompletableFuture 执行完成再执行,和上面的区别是可以使用 BiFunction 直接处理结果

    CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> "Hello");
    CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> "World");
    
    // 合并两个独立的结果
    CompletableFuture<String> combined = future1.thenCombine(future2, (s1, s2) -> s1 + " " + s2);
    // 结果将是 "Hello World"
    combined.thenAccept(System.out::println);
    
  3. 多个 CompletableFuture 比赛,当有一个完成时,取消其他

    类似 thenCombine 和 allOf,也有多任务和两个任务的版本

    CompletableFuture<Integer> future1 = CompletableFuture.supplyAsync(() -> 1);
    CompletableFuture<Integer> future2 = CompletableFuture.supplyAsync(() -> 2);
    CompletableFuture<Integer> future3 = CompletableFuture.supplyAsync(() -> 3);
     
    // 多个任务看谁跑得快
    CompletableFuture<Object> anyOf = CompletableFuture.anyOf(future1, future2, future3);
    anyOf.thenAccept(result -> System.out.println("Any result: " + result));
     
    // 两个任务看谁跑得快
    CompletableFuture<Integer> resultFuture = future1.applyToEither(future2, result -> result * 10);
    

4. 异常处理

exceptionally:任务出现异常执行

CompletableFuture.supplyAsync(() -> {
    if (true) throw new RuntimeException("Error");
    return "Success";
}).exceptionally(ex -> {
    System.err.println("Exception: " + ex.getMessage());
    return "Fallback";
});

whenComplete:不管任务是否出现异常都会执行,当异常时具有 exceptionally 的功能

CompletableFuture.supplyAsync(() -> {
    if (true) throw new RuntimeException("Something went wrong");
    return "Success";
}).whenComplete((result, ex) -> { // 任务完成或异常走这里
    if (ex != null) { // 当没有异常
        System.err.println("Failed with exception: " + ex.getMessage());
    } else { // 当发生异常
        System.out.println("Completed successfully with result: " + result);
    }
});

handle:如果同步任务(同一个线程串行任务时)如果发生异常,会影响整个链的执行

// 不使用 handle(打印结果会是null,因为异常了)
public static void main(String[] args){
    CompletableFuture.supplyAsync(() -> 5) // 任务 1( 返回5 )
            .thenApply((v) -> v + 10) // 任务 2(返回 5+10 )
            .thenApply((v) -> v / 0) // 任务 3 (返回 15/0,报错 )
            .thenApply((v) -> v + 10) // 任务 4
            .whenComplete((r, e) -> {
                System.out.println(r);
            });
}

// 使用 handle(打印结果是10)
public static void main(String[] args) {
    CompletableFuture.supplyAsync(() -> 5) // 任务 1
            .thenApply((v) -> v / 0) // 任务 2(异常)
            .thenApply((v) -> v + 10) // 任务 3
            .handle((r, e) -> { // 前面的任务(任务1、任务2、任务3任意一个)是否有异常
                if (e != null) {
                    return 0;
                } else {
                    return r;
                }
            })
            .thenApply((v) -> v + 10) // 任务 4
            .whenComplete((r, e) -> {
                System.out.println(r);
            });
}

5. 手动完成

只是是 new 出来的 CompletableFuture才需要设置状态,不过一般不会这样用

CompletableFuture<String> manualFuture = new CompletableFuture<>();

// 手动完成
manualFuture.complete("Manual Result");

// 手动异常完成
manualFuture.completeExceptionally(new RuntimeException("Failed"));

注意事项

  1. 线程池选择

    • 默认使用 ForkJoinPool.commonPool()
    • 对于I/O密集型任务,建议使用自定义线程池
    • 避免在大量任务中使用默认线程池,可能导致资源耗尽
  2. 回调执行线程

    • then* 方法默认在完成当前阶段的同一线程执行
    • 使用 *Async 方法可以在不同线程执行
  3. 异常传播

    • 异常会沿着链式调用传播,直到被处理
    • 如果不处理异常,可能导致静默失败
  4. 内存泄漏

    • 长时间运行的 CompletableFuture 链可能持有对象引用,导致内存泄漏
    • 对于不需要的结果应及时清理
  5. 超时控制

    • Java 9+ 提供了 orTimeout 和 completeOnTimeout 方法
    • Java 8 需要自行实现超时逻辑
  6. 避免阻塞

    • 尽量避免在异步任务中调用 get() 阻塞方法
    • 优先使用回调风格的编程
posted @ 2024-10-11 12:50  CyrusHuang  阅读(67)  评论(0)    收藏  举报