第11章 《CompletableFuture:组合式异步编程》

第11章 《CompletableFuture:组合式异步编程》

1、核心概念

Future的局限性

无法手动设置完成结果
  • 一旦Future任务开始执行,无法从外部手动设置其结果
  • 如果任务卡住或需要提前返回,无法干预
public static void setResultTest() throws ExecutionException, InterruptedException {
        // Future - 无法手动完成
        ExecutorService executorService = Executors.newFixedThreadPool(1);
        Future<String> future = executorService.submit(() -> {
            Thread.sleep(5000);
            return "Result";
        });

        // 无法在这里手动设置future的结果,只能等待或取消
        System.out.println("future.get():" + future.get());

        // CompletableFuture - 可以手动完成
        CompletableFuture<String> completableFuture = new CompletableFuture<>();
        executorService.submit(() -> {
            try {
                Thread.sleep(5000);
                completableFuture.complete("Result");
            } catch (InterruptedException e) {
                completableFuture.completeExceptionally(e);
            }
            completableFuture.complete("Result");
        });

        System.out.println("completableFuture set result start");
        // 可以在其他地方手动完成
        new Thread(() -> {
            completableFuture.complete("Manual Result");
        }).start();

        System.out.println("completableFuture set result end");

        System.out.println("completableFuture.get():" + completableFuture.get());

        executorService.shutdown();

    }
无法链式处理结果
  • 获取结果会阻塞调用线程
  • 无法在结果可用时自动触发后续操作
public static void handleResultTest() throws ExecutionException, InterruptedException {
        ExecutorService executorService = Executors.newFixedThreadPool(1);
        // Future - 阻塞获取结果后再处理
        Future<String> future = executorService.submit(() -> {
            Thread.sleep(5000);
            return "Hello";
        });
        String result = future.get(); // 阻塞
        result = result + " World";   // 同步处理
        System.out.println(result);

        // CompletableFuture - 非阻塞链式处理
        CompletableFuture.supplyAsync(() -> {
                    try {
                        Thread.sleep(5000);
                        return "Hello";
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                })
                .thenApply(s -> s + " World")  // 结果可用时自动处理
                .thenAccept(System.out::println); // 非阻塞
        System.out.println("not wait");
        Thread.sleep(10000);

        executorService.shutdown();
    }
无法实现响应式编程
  • 不支持回调机制
  • 无法构建复杂的异步处理流水线
// Future - 无法实现复杂流水线
Future<String> future = executor.submit(task1);
// 想要在完成后执行task2,必须阻塞等待
String result1 = future.get();
Future<String> future2 = executor.submit(() -> task2(result1));
// ...

// CompletableFuture - 流畅的流水线
CompletableFuture.supplyAsync(task1)
    .thenApplyAsync(result1 -> task2(result1))
    .thenComposeAsync(result2 -> task3(result2))
    .thenAccept(finalResult -> ...);
无法组合多个异步操作
  • 难以表达"当A和B都完成时,执行C"这样的逻辑
  • 需要手动编写复杂的等待和合并代码
    public static void combinationTest() throws ExecutionException, InterruptedException {
        ExecutorService executor = Executors.newFixedThreadPool(2);

        // 使用Supplier
        Supplier<String> task1 = () -> {
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            return "Hello1";
        };

        Supplier<String> task2 = () -> {
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            return "Hello2";
        };

        // Future - 使用Callable
        Callable<String> callable1 = () -> {
            Thread.sleep(2000);
            return "Hello1";
        };

        Callable<String> callable2 = () -> {
            Thread.sleep(3000);
            return "Hello2";
        };

        Future<String> future1 = executor.submit(callable1);
        Future<String> future2 = executor.submit(callable2);

        // 需要手动等待两个都完成
        String result1 = future1.get();
        String result2 = future2.get();
        String combined = result1 + result2;
        System.out.println(combined);

        // CompletableFuture - 使用Supplier
        CompletableFuture<String> cf1 = CompletableFuture.supplyAsync(task1, executor);
        CompletableFuture<String> cf2 = CompletableFuture.supplyAsync(task2, executor);

        cf1.thenCombine(cf2, (r1, r2) -> r1 + r2)
                .thenAccept(System.out::println);

        System.out.println("not wait");
        // 等待CompletableFuture完成
        Thread.sleep(4000);
        executor.shutdownNow();
    }
没有异常处理机制
  • 异常只能通过get()方法抛出
  • 无法在任务完成后自动处理异常
public static void handleError() throws ExecutionException, InterruptedException {
        ExecutorService executor = Executors.newFixedThreadPool(1);
        boolean someCondition = true;
        // Future - 异常处理笨拙
        Future<String> future = executor.submit(() -> {
            if (someCondition) throw new RuntimeException("Error");
            return "Success";
        });

        try {
            String result = future.get();
        } catch (ExecutionException e) {
            // 必须在这里处理异常
            System.out.println("Task failed: " + e.getCause());
        }

        // CompletableFuture - 灵活异常处理
        CompletableFuture<String> completableFuture = CompletableFuture.supplyAsync(() -> {
            if (someCondition) throw new RuntimeException("Error");
            return "Success";
        }).exceptionally(ex -> {
            System.out.println("Task failed: " + ex.getMessage());
            return "Fallback Value";
        });
        System.out.println(completableFuture.get());
        executor.shutdown();
    }

CompletableFuture优势
  • 显式完成设置(手动complete)
  • 支持异步回调
  • 提供丰富的组合方法

2、关键方法分类

1. 创建CompletableFuture

// 使用默认线程池(ForkJoinPool.commonPool())
CompletableFuture.supplyAsync(Supplier<U> supplier)
CompletableFuture.runAsync(Runnable runnable)

// 使用自定义线程池
CompletableFuture.supplyAsync(Supplier<U> supplier, Executor executor)

2. 结果处理

同步处理

thenApply(Function<T,U>)  // 转换结果
thenAccept(Consumer<T>)   // 消费结果
thenRun(Runnable)         // 不消费结果执行操作

异步处理

thenApplyAsync()
thenAcceptAsync()
thenRunAsync()

3. 组合多个Future

thenCompose(Function<T, CompletableFuture<U>>) // 扁平化,避免嵌套
thenCombine(CompletionStage<U>, BiFunction<T,U,V>) // 合并两个独立结果

4. 并行执行

allOf(CompletableFuture<?>...)  // 所有完成
anyOf(CompletableFuture<?>...)  // 任意一个完成

5. 异常处理

exceptionally(Function<Throwable, T>)  // 捕获异常并返回默认值
handle(BiFunction<T, Throwable, U>)   // 无论成功失败都处理
whenComplete(BiConsumer<T, Throwable>) // 不改变结果,只处理

3、一些练习和引发的思考总结

练习1

    /**
     * 异步任务创建
     * 创建一个 CompletableFuture,异步计算 1 到 100 的和
     * 使用 thenAccept 打印结果
     * 确保主线程等待任务完成
     */
    @Test
    public void test1() {
        // orTimeout可设置任务超时时间
        CompletableFuture<Integer> integerCompletableFuture = CompletableFuture.supplyAsync(() -> {
            int sum = 0;
            for (int i = 1; i <= 100; i++) {
                sum += i;
                try {
                    System.out.println("sleep");
                    Thread.sleep(50);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
            return sum;
        }).orTimeout(10, TimeUnit.SECONDS);
        System.out.println("异步线程开始");
        integerCompletableFuture.thenAccept(result-> System.out.println("执行thenAccept"+"结果:"+result));
        System.out.println("异步线程未完成,thenAccept未执行");
        // 确保主线程等待任务完成
        // 阻塞等待
        integerCompletableFuture.join();
        System.out.println("异步线程结束,主线程结束");
    }

CompletableFuture 的join 和get 都是阻塞等待,有什么区别

CompletableFuture 中的 join()get() 方法都是用于阻塞当前线程,直到异步任务完成并返回结果,但它们有一些关键区别:

1. 异常处理方式不同

get():

  • 抛出 受检异常InterruptedExceptionExecutionException
  • ExecutionException 包装了异步任务中抛出的原始异常
  • 必须用 try-catch 处理或声明抛出
try {
    String result = future.get();
} catch (InterruptedException | ExecutionException e) {
    // 处理异常
}

join():

  • 抛出 非受检异常CompletionException(包装了原始异常)
  • 不需要强制捕获,但如果异步任务失败会抛出运行时异常
  • 更适用于流式调用或 Lambda 表达式
String result = future.join(); // 可能抛出 CompletionException
2. 方法来源不同

get():

  • 继承自 Future 接口
  • 是 Java 5 引入的原始 Future API 的一部分

join():

  • CompletableFuture 类新增的方法
  • Java 8 引入,专为函数式编程设计
3. 使用场景

优先用 join():

  • 在链式调用或 Lambda 表达式中(避免破坏代码流畅性)
  • 当确定任务一定会成功完成时
  • 需要简洁代码的场景
CompletableFuture.supplyAsync(() -> "Hello")
               .thenApply(s -> s + " World")
               .thenAccept(System.out::println)
               .join(); // 简洁的链式调用

get():

  • 需要明确处理中断(InterruptedException)的场景
  • 需要访问原始异常时(通过 ExecutionException.getCause()
  • 与旧的 Future 代码兼容时
try {
    String result = future.get(2, TimeUnit.SECONDS); // 带超时的版本
} catch (TimeoutException e) {
    // 处理超时
}
4. 超时控制

get():

  • 有重载版本支持超时:get(long timeout, TimeUnit unit)
  • 超时抛出 TimeoutException
future.get(1, TimeUnit.SECONDS); // 等待最多1秒

join():

  • 不支持超时(如果需要超时,可以用 orTimeout()completeOnTimeout()
  • Java 9+ 可以通过 completeOnTimeout() 实现类似效果:
future.orTimeout(1, TimeUnit.SECONDS).join(); // Java 9+
5. 中断响应

get():

  • 响应线程中断(抛出 InterruptedException
  • 适合需要处理中断的场景

join():

  • 不直接响应中断(但中断会导致任务取消)
  • 更适合不关心中断的场景
最佳实践建议
  1. 在 Lambda 或流式调用中用 join()
    保持代码简洁,避免嵌套 try-catch
  2. 需要精确异常处理时用 get()
    特别是需要区分任务失败、线程中断或超时的情况。
  3. 需要超时控制时
    优先用 get(timeout, unit) 或 Java 9+ 的 orTimeout()
示例对比
// 使用 get() 的完整异常处理
try {
    String result = future.get();
    System.out.println(result);
} catch (InterruptedException e) {
    Thread.currentThread().interrupt(); // 恢复中断状态
    System.out.println("Thread interrupted");
} catch (ExecutionException e) {
    System.out.println("Task failed: " + e.getCause());
}

// 使用 join() 的简洁写法
try {
    String result = future.join();
    System.out.println(result);
} catch (CompletionException e) {
    System.out.println("Task failed: " + e.getCause());
}

总结:join() 是为函数式编程设计的更简洁版本,而 get() 提供了更细粒度的异常控制和超时支持。根据场景选择合适的方法。


CompletableFuture 超时后线程池的资源释放问题

当使用 orTimeout()get(timeout, unit) 导致 CompletableFuture 超时时,线程池中的线程是否会释放取决于具体情况。以下是详细分析:

1. orTimeout() 的行为

orTimeout() (Java 9+) 在超时发生时:

  • 会将 CompletableFuture 标记为以 TimeoutException 完成
  • 但不会自动取消底层任务(除非你显式配置)
CompletableFuture.supplyAsync(() -> {
    try {
        Thread.sleep(5000); // 长时间运行任务
        return "Result";
    } catch (InterruptedException e) {
        System.out.println("任务被中断");
        return null;
    }
})
.orTimeout(1, TimeUnit.SECONDS) // 1秒超时
.exceptionally(ex -> "超时处理");

在这个例子中:

  • 1秒后会触发超时
  • 但线程池中的任务会继续运行直到完成(5秒)
  • 线程在这期间不会被释放
2. 如何确保线程释放
方案1:使用 completeOnTimeout() + 取消任务
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
    try {
        Thread.sleep(5000);
        return "Result";
    } catch (InterruptedException e) {
        System.out.println("任务被取消");
        return null;
    }
});

future.completeOnTimeout("Timeout Default", 1, TimeUnit.SECONDS);

// 超时后取消任务
future.whenComplete((res, ex) -> {
    if (ex instanceof CompletionException && 
        ex.getCause() instanceof TimeoutException) {
        future.cancel(true); // true表示中断线程
    }
});
方案2:结合 ExecutorService 显式关闭
ExecutorService executor = Executors.newFixedThreadPool(1);

CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
    try {
        Thread.sleep(5000);
        return "Result";
    } catch (InterruptedException e) {
        Thread.currentThread().interrupt();
        return null;
    }
}, executor)
.orTimeout(1, TimeUnit.SECONDS);

future.whenComplete((res, ex) -> {
    if (ex != null) {
        executor.shutdownNow(); // 强制关闭线程池
    }
});
3. 关键结论
  1. 默认情况下超时不会自动释放线程
    • 超时只是标记 future 完成状态
    • 底层任务线程会继续运行
  2. 需要主动取消
    • 使用 cancel(true) 中断线程
    • 或直接关闭线程池 (shutdownNow())
  3. 最佳实践
    • 对长时间任务实现中断机制
    • 使用 try-catch 处理 InterruptedException
    • 超时后清理资源
4. 完整示例
ExecutorService executor = Executors.newFixedThreadPool(2);

CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
    try {
        // 检查中断状态
        for (int i = 0; i < 10; i++) {
            if (Thread.currentThread().isInterrupted()) {
                throw new InterruptedException();
            }
            Thread.sleep(1000); // 分阶段工作
        }
        return "Done";
    } catch (InterruptedException e) {
        System.out.println("任务被中断");
        return null;
    }
}, executor);

// 设置超时并处理
future.orTimeout(3, TimeUnit.SECONDS)
      .whenComplete((res, ex) -> {
          if (ex != null) {
              future.cancel(true); // 中断任务
          }
          executor.shutdown(); // 关闭线程池
      });

这样能确保:

  1. 3秒超时后中断任务
  2. 释放线程池资源
  3. 避免线程泄漏

练习2

    /**
     * 异常处理
     * 创建一个可能抛出异常的 CompletableFuture 任务
     * 使用 exceptionally 提供默认值 "Error occurred" 当异常发生时
     * 验证异常和正常情况下的输出
     */
    @Test
    public void test2() {
        CompletableFuture<Integer> completableFuture = CompletableFuture.supplyAsync(() -> {
            int i = new Random().nextInt(100);
            if (i > 50) {
                throw new RuntimeException("Error occurred");
            }
            return i;
        }).exceptionally(throwable -> {
            System.out.println("Error occurred: " + throwable.getMessage());
            return 0;
        });
        completableFuture.thenAccept(result -> System.out.println("结果: " + result));
        completableFuture.join();
    }
exceptionally 能捕获哪些异常
  • 任务中显式抛出的 RuntimeException(如代码所示)
  • 任何 Throwable(包括 Error,除非显式过滤)
  • 异步执行过程中发生的其他意外错误(如 OutOfMemoryError

如果需要区分异常类型,可按如下改进

.exceptionally(throwable -> {
    Throwable rootCause = throwable.getCause(); // 获取原始异常
    if (rootCause instanceof RuntimeException) {
        System.out.println("业务异常: " + rootCause.getMessage());
    } else if (rootCause instanceof Error) {
        System.out.println("系统错误: " + rootCause);
    }
    return 0;
})

练习3

    /**
     * 结果转换
     * 异步获取一个随机数 (0-100)
     * 使用 thenApply 将结果乘以 2
     * 使用 thenAccept 打印最终结果
     */
    @Test
    public void test3() {
        CompletableFuture<Void> voidCompletableFuture = CompletableFuture.supplyAsync(() -> {
                    int i = new Random().nextInt(100);
                    System.out.println("随机数: " + i);
                    try {
                        Thread.sleep(5000);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                    return i;
                }).thenApply(i -> i * 2)
                .thenAccept(result -> System.out.println("结果: " + result));
        System.out.println("等待异步任务");
        voidCompletableFuture.join();
        System.out.println("异步任务结束,主线程结束");
    }
supplyAsync和runAsync的使用场景
如何选择?可根据以下问题决定:
  1. 是否需要任务返回值?
    • 是 → supplyAsync
    • 否 → runAsync
  2. 任务是否会产生需要处理的异常?
    • supplyAsync 可通过返回结果传递异常
    • runAsync 需通过 whenComplete()/handle() 处理
  3. 是否是纯副作用操作?
    • 如日志、通知等 → runAsync
两者对比

即使不需要返回值,技术上也可以使用 supplyAsync但根据场景选择更合适的方法能让代码更清晰、语义更明确

1. 技术可行性

从语法层面,完全可以用 supplyAsync 替代 runAsync,只需返回 null 或任意虚拟值:

// 不推荐但可行的写法
CompletableFuture.supplyAsync(() -> {
    doSomethingWithoutResult(); // 无返回值的操作
    return null; // 强制返回null
});

// 等效的runAsync写法(推荐)
CompletableFuture.runAsync(() -> doSomethingWithoutResult());

2. 为什么推荐用 runAsync

(1)语义清晰性

  • runAsync 明确表示这是一个 无返回值的副作用操作(如日志、通知、清理资源等)
  • supplyAsync 会让人误以为需要处理返回值

(2)避免不必要的包装

  • supplyAsync 需要强制返回一个值(即使是 null),属于冗余代码
  • runAsync 直接使用 Runnable,更符合无返回值场景的语义

(3)类型系统更干净

  • runAsync 返回 CompletableFuture<Void>,明确告知调用者“无结果”
  • supplyAsync 返回 CompletableFuture<U>,可能误导他人以为需要处理返回值

3. 何时可以破例使用 supplyAsync

场景:需要同时满足以下条件时

  1. 主逻辑不关心返回值,但 需要知道任务是否完成
  2. 任务内部 可能抛出异常,且需要捕获异常信息
// 示例:用supplyAsync捕获异常信息(虽然不关心返回值)
CompletableFuture.supplyAsync(() -> {
    doSomethingRisky();
    return null; // 不关心返回值
}).exceptionally(ex -> {
    System.err.println("任务失败: " + ex.getMessage());
    return null;
});

对比 runAsync 的异常处理:

CompletableFuture.runAsync(() -> doSomethingRisky())
    .whenComplete((v, ex) -> {
        if (ex != null) {
            System.err.println("任务失败: " + ex.getMessage());
        }
    });

4. 性能差异
  • 无实质区别:两者底层使用相同的线程池机制
  • 微小开销supplyAsync 多了一次 null 的包装/传递,但可忽略不计

5. 最佳实践总结
场景推荐方法示例
不关心结果,也不关心异常runAsync日志记录、触发通知
不关心结果,但需要异常处理runAsync + whenComplete异步清理资源时需要错误日志
需要返回虚拟值以传递状态supplyAsync某些框架强制要求非Void泛型
需要链式调用统一风格根据团队约定选择如果链中其他操作用 supplyAsync 可保持风格一致

6. 完整示例对比

使用 runAsync(推荐)

// 清晰的“无返回值”语义
CompletableFuture.runAsync(() -> {
    log.info("Async operation started");
    cleanTempFiles(); // 无返回值的操作
}).whenComplete((v, ex) -> {
    if (ex != null) {
        log.error("Cleanup failed", ex);
    }
});

使用 supplyAsync(不推荐但可行)

// 语义模糊(为什么用supplyAsync却返回null?)
CompletableFuture.supplyAsync(() -> {
    log.info("Async operation started");
    cleanTempFiles();
    return null; // 冗余
}).exceptionally(ex -> {
    log.error("Cleanup failed", ex);
    return null;
});

结论
  • 优先用 runAsync:无返回值时更符合语义,代码更干净
  • 特殊场景可用 supplyAsync:如需要统一异常处理风格或框架限制时
  • 强制用 supplyAsync 返回 null 是代码异味(除非有充分理由)

练习4

    /**
     * 任务链式调用
     * 创建第一个任务异步生成一个随机字符串
     * 第二个任务将字符串转换为大写
     * 第三个任务将字符串反转
     * 最后打印最终结果
     */
    @Test
    public void test4() {

        CompletableFuture.supplyAsync(() -> "hello world")
                .thenApply(String::toUpperCase)
                .thenApply(s -> new StringBuilder(s).reverse().toString())
                .thenAccept(result -> System.out.println("结果: " + result))
                .join();
        System.out.println("异步任务结束,主线程结束");
    }

练习5

    /**
     * 两个独立任务组合
     * 创建两个独立任务:一个计算圆的面积(给定半径),一个计算正方形面积(给定边长)
     * 使用 thenCombine 将两个结果相加
     * 打印总面积
     */
    @Test
    public void test5() {
        CompletableFuture<Double> circleAreaFuture = CompletableFuture.supplyAsync(() -> {
            double radius = 5.0;
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            return Math.PI * radius * radius;
        });
        CompletableFuture<Double> squareAreaFuture = CompletableFuture.supplyAsync(() -> {
            double sideLength = 4.0;
            System.out.println("计算正方形面积完成");
            return sideLength * sideLength;
        });
        System.out.println("等待异步任务");
        circleAreaFuture.thenCombine(squareAreaFuture, (circleArea, squareArea) -> circleArea + squareArea)
                .thenAccept(totalArea -> System.out.println("总面积: " + totalArea))
                .join();
        System.out.println("异步任务结束,主线程结束");
    }

练习6

    /**
     * 扁平化处理
     * 创建第一个任务异步返回一个用户ID
     * 使用 thenCompose 根据用户ID异步获取用户详细信息
     * 打印用户信息
     */
    @Test
    public void test6() {
        CompletableFuture.supplyAsync(() -> 1)
                .thenCompose(userId -> CompletableFuture.supplyAsync(() -> new User(Integer.toString(userId))))
                .thenAccept(user -> System.out.println("用户信息: " + user))
                .join();
    }
}

class User {
    String id;
    String name;

    User(String id) {
        this.id = id;
        this.name = "User-" + id;
    }

    @Override
    public String toString() {
        return "User{id='" + id + "', name='" + name + "'}";
    }
}

thenCompose是Java 8中CompletableFuture类的一个重要方法,用于将多个异步操作串联起来,形成异步操作的流水线。

thenCompose方法用于将一个CompletableFuture的结果作为另一个CompletableFuture的输入,实现异步操作的链式调用。它类似于Stream API中的flatMap操作。

thenCompose实际应用场景
  1. 依赖异步操作:当一个异步操作的结果是另一个异步操作的输入时
  2. 避免嵌套Future:解决CompletableFuture<CompletableFuture<T>>的问题(可用来解决嵌套Future问题)
  3. 构建异步流水线:创建一系列依赖的异步操作链

练习7

    // 模拟网络请求的方法
    private static String mockNetworkRequest(String taskName, int delaySeconds) {
        try {
            TimeUnit.SECONDS.sleep(delaySeconds);
            return taskName + " completed in " + delaySeconds + " seconds";
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 多任务并行
     * 创建3个异步任务,分别模拟不同时间的网络请求(1s, 2s, 3s)
     * 使用 allOf 等待所有任务完成
     * 收集并打印所有结果
     */
    @Test
    public void test7() {
        CompletableFuture<String> completableFuture1 = CompletableFuture.supplyAsync(() -> mockNetworkRequest("Task1", 1));
        CompletableFuture<String> completableFuture2 = CompletableFuture.supplyAsync(() -> mockNetworkRequest("Task2", 2));
        CompletableFuture<String> completableFuture3 = CompletableFuture.supplyAsync(() -> mockNetworkRequest("Task3", 3));

        CompletableFuture<Void> voidCompletableFuture = CompletableFuture.allOf(completableFuture1, completableFuture2, completableFuture3);

//        voidCompletableFuture.thenAccept(v -> {
//            System.out.println("所有任务完成");
//            System.out.println("结果: " + completableFuture1.join());
//            System.out.println("结果: " + completableFuture2.join());
//            System.out.println("结果: " + completableFuture3.join());
//        }).join();

//        voidCompletableFuture.thenRun(() -> {
//            System.out.println("所有任务完成");
//            System.out.println("结果: " + completableFuture1.join());
//            System.out.println("结果: " + completableFuture2.join());
//            System.out.println("结果: " + completableFuture3.join());
//        }).join();

        // 先收集结果,在打印
        List<CompletableFuture<String>> futures = Arrays.asList(completableFuture1, completableFuture2, completableFuture3);

        CompletableFuture<Void> allDone = CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]));

        CompletableFuture<List<String>> allResults = allDone.thenApply(v ->
                futures.stream()
                        .map(CompletableFuture::join)
                        .collect(Collectors.toList())
        );

        // 打印结果
        allResults.thenAccept(results -> {
            System.out.println("\n所有任务完成");
            results.forEach(result -> System.out.println("结果: " + result));
        }).join();
    }
thenAcceptthenRun 的区别及使用场景

thenAcceptthenRun 都是 CompletableFuture 的回调方法,但它们有不同的用途:

主要区别
方法参数返回值使用场景
thenRun当前阶段完成后执行一个不需要参数也不返回值的动作
thenAccept前一阶段的结果当前阶段完成后消费前一阶段的结果
区别分析
  1. 参数不同
    • thenAccept 接收一个 Consumer,会传入 allOf 的结果(这里是 Void 类型)
    • thenRun 接收一个 Runnable,不接受任何参数
  2. 适用性
    • 在这个特定场景下,两者都可以工作,因为:
      • allOf 返回的是 VoidthenAccept 的参数 v 实际上是 null
      • 你并没有真正使用这个参数
  3. 代码风格
    • 使用 thenRun 更合适,因为你不需要前一阶段的结果
    • 使用 thenAccept 并接受一个不使用的参数,在代码风格上不够优雅
推荐用法

在这个场景下,thenRun 是更好的选择,因为:

  1. 你不需要 allOf 的结果(它只是表示完成状态)
  2. 代码更清晰地表达了"当所有任务完成后,执行某些操作"的意图
  3. 避免了接收一个不使用的参数
使用建议
  • 当你需要基于前一阶段的结果执行操作时,使用 thenAccept
  • 当你只需要在前一阶段完成后执行操作,而不关心其结果时,使用 thenRun
  • 在测试代码中,通常使用 join() 来阻塞等待结果,但在生产代码中应避免这样做

对比 get()join()

join() 会:

  1. 阻塞当前线程,直到 `task1 完成(如果还没完成的话)。
  2. 返回 task 的结果
关键点:
  1. join() 是阻塞的:它会等待任务完成(如果还没完成的话),然后返回结果。
  2. 返回值就是 supplyAsyncSupplier 的返回值
    • 你的 Supplier() -> mockNetworkRequest("Task1", 1),所以 join() 返回的就是 mockNetworkRequest 的返回值。
  3. 如果任务抛出异常,join() 会抛出 CompletionException
    • 例如,如果 mockNetworkRequest 里抛出异常,task1.join() 会抛出 CompletionException
方法是否受检异常是否抛出 InterruptedException是否抛出 ExecutionException是否抛出 CompletionException
get()✅ 是 (InterruptedException, ExecutionException)✅ 是✅ 是❌ 否
join()❌ 否❌ 否❌ 否✅ 是
  • join() 更简洁(不需要 try-catch 受检异常),适合测试或确定任务不会失败的情况。
  • get() 更适合生产代码,因为它能更明确地处理异常。
总结:

task1.join() 返回的是 mockNetworkRequestreturn 值,因为:

  1. supplyAsync 会执行传入的 Supplier 并返回它的计算结果。
  2. join() 会等待任务完成并返回这个计算结果。

练习8

    /**
     * 最快响应
     * 创建3个异步任务,模拟不同响应时间的服务(随机1-3秒)
     * 使用 anyOf 获取第一个完成的结果
     * 打印最快响应的结果
     */
    @Test
    public void test8() {
        CompletableFuture<String> completableFuture1 = CompletableFuture.supplyAsync(() -> mockNetworkRequest("Task1", new Random().nextInt(3) + 1));
        CompletableFuture<String> completableFuture2 = CompletableFuture.supplyAsync(() -> mockNetworkRequest("Task2", new Random().nextInt(3) + 1));
        CompletableFuture<String> completableFuture3 = CompletableFuture.supplyAsync(() -> mockNetworkRequest("Task3", new Random().nextInt(3) + 1));
        CompletableFuture<Object> anyOf = CompletableFuture.anyOf(completableFuture1, completableFuture2, completableFuture3);
        anyOf.thenAccept(result -> System.out.println("最快响应的结果: " + result)).join();
    }

练习9

    /**
     * 超时处理
     * 创建一个可能长时间运行的任务(模拟5秒)
     * 使用 completeOnTimeout 设置2秒超时和默认值
     * 验证超时和正常完成两种情况
     */
    @Test
    public void test9() {
        CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
            try {
                TimeUnit.SECONDS.sleep(5);
                return "Task completed";
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        });
        future.completeOnTimeout("Task timed out", 2, TimeUnit.SECONDS)
                .thenAccept(result -> System.out.println("Result: " + result)).join();

//        future.completeOnTimeout("Task timed out", 6, TimeUnit.SECONDS)
//                .thenAccept(result -> System.out.println("Result: " + result)).join();
    }
超时后资源释放的问题
completeOnTimeout 的行为特点
  1. 非中断性completeOnTimeout 不会中断或取消原始任务
    • 它只是当超时发生时,让返回的新 CompletableFuture 提前完成
    • 原始任务会继续在后台执行直到完成
  2. 结果处理
    • 如果原始任务在超时前完成,使用原始任务的结果
    • 如果超时先发生,使用指定的默认值(“Task timed out”)
  3. 资源消耗
    • 即使超时返回了默认值,原始线程仍会继续执行
    • 这可能导致资源浪费(线程、内存等)
如何真正终止任务?

如果需要真正终止长时间运行的任务,应该使用以下模式:

方案1:使用 orTimeout + 取消处理
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
    try {
        TimeUnit.SECONDS.sleep(5);
        return "Task completed";
    } catch (InterruptedException e) {
        throw new RuntimeException("Task interrupted", e);
    }
});

future.orTimeout(2, TimeUnit.SECONDS)  // 超时抛出 TimeoutException
     .exceptionally(ex -> {
         if (ex.getCause() instanceof TimeoutException) {
             // 这里可以添加资源清理逻辑
             return "Task timed out and canceled";
         }
         return "Other error occurred";
     })
     .thenAccept(System.out::println)
     .join();
方案2:配合中断机制
CompletableFuture<String> future = new CompletableFuture<>();
Thread taskThread = new Thread(() -> {
    try {
        TimeUnit.SECONDS.sleep(5);
        future.complete("Task completed");
    } catch (InterruptedException e) {
        future.completeExceptionally(new RuntimeException("Task interrupted"));
    }
});
taskThread.start();

future.completeOnTimeout("Task timed out", 2, TimeUnit.SECONDS)
     .thenAccept(result -> {
         System.out.println("Result: " + result);
         if (result.equals("Task timed out")) {
             taskThread.interrupt();  // 中断线程
         }
     })
     .join();
关键结论
  1. completeOnTimeout 不会终止原始任务 - 它只是提前返回默认值
  2. 资源仍在消耗 - 原始任务会继续运行直到完成
  3. 如需真正终止 - 需要额外实现中断逻辑或使用 orTimeout + 异常处理
  4. 生产环境建议 - 对于可能长时间运行的任务,应该实现可中断的设计

练习10

    /**
     * 复杂流水线
     * 模拟电商订单处理流程:
     * 异步创建订单
     * 并行检查库存和用户信用
     * 当两者都通过时,异步进行支付
     * 支付成功后发送通知
     * 在每个步骤打印日志
     * 处理可能的异常情况
     */
    @Test
    public void test10() {
        CompletableFuture<String> orderProcessing = CompletableFuture.supplyAsync(() -> {
            System.out.println("1.创建订单");
            sleep(1000);
            return "Order-123";
        });

        CompletableFuture<Boolean> inventoryCheck = orderProcessing.thenApplyAsync(orderId -> {
            System.out.println("2.开始检查库存");
            sleep(1500);
            return true;
        });
        CompletableFuture<Boolean> creditCheck = orderProcessing.thenApplyAsync(orderId -> {
            System.out.println("3.开始检查信用");
            sleep(2000);
            return true;
        });
        inventoryCheck.thenCombine(creditCheck, (inventoryPassStatus, creditPassStatus) -> {
            System.out.println("库存检查情况:" + inventoryPassStatus + ",信用检查情况:" + creditPassStatus);
            return inventoryPassStatus && creditPassStatus;
        }).thenCompose(passStatus -> {
            if (passStatus) {
                System.out.println("4.开始支付");
                return CompletableFuture.supplyAsync(() -> {
                    sleep(1000);
                    return "Payment Successful";
                });
            } else {
                return CompletableFuture.completedFuture("Payment Failed");
            }
        }).thenAccept(paymentStatus -> {
            System.out.println("5.支付结果:" + paymentStatus);
            if ("Payment Successful".equals(paymentStatus)) {
                System.out.println("6.开始发送通知");
                sleep(500);
                System.out.println("7.通知发送完成");
            } else {
                System.out.println("6.支付失败,无需发送通知");
            }
        }).exceptionally(throwable -> {
            System.out.println("发生异常:" + throwable.getMessage());
            return null;
        }).join();
    }

    private static void sleep(int millis) {
        try {
            Thread.sleep(millis);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值