CompletableFuture

本文详细介绍了Java CompletableFuture的使用,包括如何通过CompletableFuture实现异步任务的回调,以及如何进行串行和并行任务的组合。通过示例展示了如何在成功时调用thenAccept,异常时调用exceptionally,并利用thenApplyAsync进行任务串联,以及如何通过anyOf和allOf进行任务并行。CompletableFuture提供了复杂异步流程控制的能力。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

使用Future获得异步执行结果时,要么调用阻塞方法get(),要么轮询看isDone()是否为true,这两种方法都不是很好,因为主线程也会被迫等待。

从Java 8开始引入了CompletableFuture,它针对Future做了改进,可以传入回调对象,当异步任务完成或者发生异常时,自动调用回调对象的回调方法。

我们以获取股票价格为例,看看如何使用CompletableFuture

public class Main {
    public static void main(String[] args) throws Exception {
        // 创建异步执行任务:
        CompletableFuture<Double> cf = CompletableFuture.supplyAsync(Main::fetchPrice);
        // 如果执行成功:
        cf.thenAccept((result) -> {
            System.out.println("price: " + result);
        });
        // 如果执行异常:
        cf.exceptionally((e) -> {
            e.printStackTrace();
            return null;
        });
        // 主线程不要立刻结束,否则CompletableFuture默认使用的线程池会立刻关闭:
        Thread.sleep(200);
    }

    static Double fetchPrice() {
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
        }
        if (Math.random() < 0.3) {
            throw new RuntimeException("fetch price failed!");
        }
        return 5 + Math.random() * 20;
    }
}

 Run

创建一个CompletableFuture是通过CompletableFuture.supplyAsync()实现的,它需要一个实现了Supplier接口的对象:

public interface Supplier<T> {
    T get();
}

这里我们用lambda语法简化了一下,直接传入Main::fetchPrice,因为Main.fetchPrice()静态方法的签名符合Supplier接口的定义(除了方法名外)。

紧接着,CompletableFuture已经被提交给默认的线程池执行了,我们需要定义的是CompletableFuture完成时和异常时需要回调的实例。完成时,CompletableFuture会调用Consumer对象:

public interface Consumer<T> {
    void accept(T t);
}

异常时,CompletableFuture会调用Function对象:

public interface Function<T, R> {
    R apply(T t);
}

这里我们都用lambda语法简化了代码。

可见CompletableFuture的优点是:

  • 异步任务结束时,会自动回调某个对象的方法;
  • 异步任务出错时,会自动回调某个对象的方法;
  • 主线程设置好回调后,不再关心异步任务的执行。

如果只是实现了异步回调机制,我们还看不出CompletableFuture相比Future的优势。CompletableFuture更强大的功能是,多个CompletableFuture可以串行执行,例如,定义两个CompletableFuture,第一个CompletableFuture根据证券名称查询证券代码,第二个CompletableFuture根据证券代码查询证券价格,这两个CompletableFuture实现串行操作如下:

public class Main {
    public static void main(String[] args) throws Exception {
        // 第一个任务:
        CompletableFuture<String> cfQuery = CompletableFuture.supplyAsync(() -> {
            return queryCode("中国石油");
        });
        // cfQuery成功后继续执行下一个任务:
        CompletableFuture<Double> cfFetch = cfQuery.thenApplyAsync((code) -> {
            return fetchPrice(code);
        });
        // cfFetch成功后打印结果:
        cfFetch.thenAccept((result) -> {
            System.out.println("price: " + result);
        });
        // 主线程不要立刻结束,否则CompletableFuture默认使用的线程池会立刻关闭:
        Thread.sleep(2000);
    }

    static String queryCode(String name) {
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
        }
        return "601857";
    }

    static Double fetchPrice(String code) {
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
        }
        return 5 + Math.random() * 20;
    }
}

 Run

除了串行执行外,多个CompletableFuture还可以并行执行。例如,我们考虑这样的场景:

同时从新浪和网易查询证券代码,只要任意一个返回结果,就进行下一步查询价格,查询价格也同时从新浪和网易查询,只要任意一个返回结果,就完成操作:

public class Main {
    public static void main(String[] args) throws Exception {
        // 两个CompletableFuture执行异步查询:
        CompletableFuture<String> cfQueryFromSina = CompletableFuture.supplyAsync(() -> {
            return queryCode("中国石油", "https://finance.sina.com.cn/code/");
        });
        CompletableFuture<String> cfQueryFrom163 = CompletableFuture.supplyAsync(() -> {
            return queryCode("中国石油", "https://money.163.com/code/");
        });

        // 用anyOf合并为一个新的CompletableFuture:
        CompletableFuture<Object> cfQuery = CompletableFuture.anyOf(cfQueryFromSina, cfQueryFrom163);

        // 两个CompletableFuture执行异步查询:
        CompletableFuture<Double> cfFetchFromSina = cfQuery.thenApplyAsync((code) -> {
            return fetchPrice((String) code, "https://finance.sina.com.cn/price/");
        });
        CompletableFuture<Double> cfFetchFrom163 = cfQuery.thenApplyAsync((code) -> {
            return fetchPrice((String) code, "https://money.163.com/price/");
        });

        // 用anyOf合并为一个新的CompletableFuture:
        CompletableFuture<Object> cfFetch = CompletableFuture.anyOf(cfFetchFromSina, cfFetchFrom163);

        // 最终结果:
        cfFetch.thenAccept((result) -> {
            System.out.println("price: " + result);
        });
        // 主线程不要立刻结束,否则CompletableFuture默认使用的线程池会立刻关闭:
        Thread.sleep(200);
    }

    static String queryCode(String name, String url) {
        System.out.println("query code from " + url + "...");
        try {
            Thread.sleep((long) (Math.random() * 100));
        } catch (InterruptedException e) {
        }
        return "601857";
    }

    static Double fetchPrice(String code, String url) {
        System.out.println("query price from " + url + "...");
        try {
            Thread.sleep((long) (Math.random() * 100));
        } catch (InterruptedException e) {
        }
        return 5 + Math.random() * 20;
    }
}

 Run

上述逻辑实现的异步查询规则实际上是:

┌─────────────┐ ┌─────────────┐
│ Query Code  │ │ Query Code  │
│  from sina  │ │  from 163   │
└─────────────┘ └─────────────┘
       │               │
       └───────┬───────┘
               ▼
        ┌─────────────┐
        │    anyOf    │
        └─────────────┘
               │
       ┌───────┴────────┐
       ▼                ▼
┌─────────────┐  ┌─────────────┐
│ Query Price │  │ Query Price │
│  from sina  │  │  from 163   │
└─────────────┘  └─────────────┘
       │                │
       └────────┬───────┘
                ▼
         ┌─────────────┐
         │    anyOf    │
         └─────────────┘
                │
                ▼
         ┌─────────────┐
         │Display Price│
         └─────────────┘

除了anyOf()可以实现“任意个CompletableFuture只要一个成功”,allOf()可以实现“所有CompletableFuture都必须成功”,这些组合操作可以实现非常复杂的异步流程控制。

最后我们注意CompletableFuture的命名规则:

  • xxx():表示该方法将继续在已有的线程中执行;
  • xxxAsync():表示将异步在线程池中执行。

练习

从下载练习:使用CompletableFuture (推荐使用IDE练习插件快速下载)

小结

CompletableFuture可以指定异步处理流程:

  • thenAccept()处理正常结果;
  • exceptional()处理异常结果;
  • thenApplyAsync()用于串行化另一个CompletableFuture
  • anyOf()allOf()用于并行化多个CompletableFuture
### CompletableFuture 的用法及错误处理 CompletableFutureJava 8 引入的一个强大工具,用于处理异步编程和复杂的任务链。它不仅支持异步执行任务,还提供了丰富的 API 来处理任务的结果或异常。 #### 基本概念 CompletableFuture 是一个 Future 的增强版本,允许开发者通过回调函数来处理任务完成后的结果或异常[^1]。它支持多种方法来组合多个异步任务,例如 `thenApply`、`thenAccept`、`exceptionally` 等。 #### 创建 CompletableFuture 可以使用以下几种方式创建 CompletableFuture: - 使用 `runAsync` 方法运行不返回结果的任务。 ```java CompletableFuture<Void> future = CompletableFuture.runAsync(() -> { System.out.println("Running in a separate thread: " + Thread.currentThread().getName()); }); future.join(); // Wait for the task to complete ``` - 使用 `supplyAsync` 方法运行返回结果的任务。 ```java CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> { return "Result from supplyAsync"; }); System.out.println(future.join()); // Output the result ``` #### 链式调用 CompletableFuture 支持链式调用来处理任务的结果或异常。例如: - `thenApply`:对前一个任务的结果进行转换。 ```java CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> "Hello") .thenApply(s -> s + " World"); System.out.println(future.join()); // Output: Hello World ``` - `thenAccept`:消费前一个任务的结果。 ```java CompletableFuture.supplyAsync(() -> "Hello World") .thenAccept(System.out::println); ``` - `thenCompose`:将一个 CompletableFuture 的结果作为另一个 CompletableFuture 的输入。 ```java CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> "Hello") .thenCompose(s -> CompletableFuture.supplyAsync(() -> s + " World")); System.out.println(future.join()); // Output: Hello World ``` #### 错误处理 当任务抛出异常时,可以使用 `exceptionally` 或 `handle` 方法来捕获并处理异常。 - `exceptionally`:为任务的异常提供一个默认值。 ```java CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> { throw new RuntimeException("Task failed"); }).exceptionally(ex -> "Default Value on Exception: " + ex.getMessage()); System.out.println(future.join()); // Output: Default Value on Exception: Task failed ``` - `handle`:为任务的成功或失败提供统一的处理逻辑。 ```java CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> { throw new RuntimeException("Task failed"); }).handle((result, ex) -> { if (ex != null) { return "Handled Exception: " + ex.getMessage(); } return "Result: " + result; }); System.out.println(future.join()); // Output: Handled Exception: Task failed ``` #### 并行任务 可以通过 `allOf` 和 `anyOf` 方法来组合多个 CompletableFuture。 - `allOf`:等待所有任务完成。 ```java CompletableFuture<Void> allFutures = CompletableFuture.allOf( CompletableFuture.runAsync(() -> System.out.println("Task 1")), CompletableFuture.runAsync(() -> System.out.println("Task 2")) ); allFutures.join(); // Wait for both tasks to complete ``` - `anyOf`:等待任意一个任务完成。 ```java CompletableFuture<Object> anyFuture = CompletableFuture.anyOf( CompletableFuture.supplyAsync(() -> { try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) {} return "Task 1"; }), CompletableFuture.supplyAsync(() -> { try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) {} return "Task 2"; }) ); System.out.println(anyFuture.join()); // Output: Task 2 ``` #### 实际应用 在实际开发中,CompletableFuture 可以用于异步加载数据、并发任务调度等场景。例如,结合数据库查询和缓存操作: ```java CompletableFuture<String> dbFuture = CompletableFuture.supplyAsync(() -> { // Simulate database query try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) {} return "Data from DB"; }); CompletableFuture<String> cacheFuture = CompletableFuture.supplyAsync(() -> { // Simulate cache lookup try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) {} return "Data from Cache"; }); CompletableFuture<Void> combinedFuture = dbFuture.thenAcceptBoth(cacheFuture, (dbResult, cacheResult) -> { System.out.println("DB Result: " + dbResult); System.out.println("Cache Result: " + cacheResult); }); combinedFuture.join(); ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值