CompletableFuture从入门、踩坑、迷茫、到精通(全网看这一篇够了)

1.先来聊聊为什么要使用CompletableFuture?

那就先聊聊Future的局限性:

Future表示一个异步计算的结果。提供了isDone()来检测计算是否已经完成,并且在计算结束后,可以通过get()方法来获取计算结果。但是从框架本身会存在下列问题:

  • 多个任务进行链式调用无法支持:如果你希望在计算任务完成后执行特定动作,比如发邮件,但Future却没有提供这样的能力;
  • 组合任务编排能力无法实现:如果你运行了100个任务,并且按照一定的顺序编排起来,future没有办法去实现。
  • 没有异常处理的钩子:Future接口中没有关于异常处理的方法;

2. 如何高效利用CompletableFuture实现任务编排?

先整两个方法:打印时间方法和睡眠方法,方便后面直接调用

public static ExecutorService executor = Executors.newCachedThreadPool();

private static void sleep(long sleepTime) {
    try {
        Thread.sleep(sleepTime);
    } catch (InterruptedException e) {
        e.printStackTrace();
    };
}

private static void currentDate(String str) {
    // 创建一个Date对象,它包含了当前时间
    Date now = new Date();
    // 创建一个SimpleDateFormat对象,用于指定输出格式
    SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    // 使用format方法将Date对象格式化为字符串
    String currentTime = dateFormat.format(now);
    // 打印当前时间
    System.out.println(str + "是: " + currentTime);
}
  1. supplyAsync的使用
    //get()方法抛出的是经过检查的异常,ExecutionException, InterruptedException需要用户手动处理或者捕获异常
    @Test
    public void test1() {
        CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
            sleep(1000);
            return "hello,word";
        },executor);

        String s = null;
        try {
            s = future.get();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
        System.out.println(s);
    }
  1. 正常执行和发生异常如何处理
//CompletableFuture的计算结果完成,需要进行的操作 whenComplete
//抛出异常时,需要进行的操作exceptionally
@Test
public void test2() {
    CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
        sleep(1000);
        int i = 1/0;
        return "hello,word";
    });

    //正常执行完
    future.whenComplete((s, throwable) -> System.out.println(s));

    //发生异常
    future.exceptionally(t -> {
        System.out.println("执行失败:" + t.getMessage());
        return "异常xxxx";
    });
    future.join();
}
  1. 两个任务组合

其实下面的代码可以执行下,会发现整体使用的时间会是执行时间较长的任务的时间

@Test
public void test3() throws ExecutionException, InterruptedException {
    currentDate("执行前");
    CompletableFuture<Integer> future1 = CompletableFuture.supplyAsync(() -> {
        sleep(1000);
        return 1;
    });

    CompletableFuture<Integer> future2 = CompletableFuture.supplyAsync(() -> {
        sleep(2000);
        return 2;
    });
    CompletableFuture<Integer> future = future1.thenCombine(future2, Integer::sum);
    Integer result = future.get();
    System.out.println(result);
    currentDate("执行后");
}

执行结果如下:

可以从结果看到整体执行时间就是执行任务较长的时间,并且直接结果为3

  1. 多任务组合

两个线程任务相比较,先获得执行结果的,就对该结果进行下一步的转化操作。

@Test
public void test5() throws ExecutionException, InterruptedException {
    CompletableFuture<Integer> future1 = CompletableFuture.supplyAsync(() -> {
        int number = new Random().nextInt(10);
        System.out.println("future1 start:" + number);
        sleep(number);
        System.out.println("future1 end:" + number);
        return number;
    });

    CompletableFuture<Integer> future2 = CompletableFuture.supplyAsync(() -> {
        int number = new Random().nextInt(10);
        System.out.println("future2 start:" + number);
        sleep(number);
        System.out.println("future2 end:" + number);
        return number;
    });
    CompletableFuture<Integer> future = future1.applyToEither(future2, new Function<Integer, Integer>() {
        @Override
        public Integer apply(Integer number) {
            System.out.println("最快结果:" + number);
            return number;
        }
    });

    Integer result = future.get();
    System.out.println("结果值为 " + result);
}

执行结果

future1和future2的睡眠时间是一个随机数,从执行日志可以看到生成的数小,睡眠时间越少,越先被执行完,因此future拿到的是最先执行完的结果5.

  1. 个数不定的CompletableFuture

可以使用allOf().join等待所有异步任务的执行。

@Test
public void test6() throws ExecutionException, InterruptedException {
    currentDate("执行前");
    CompletableFuture<Integer> future1 = CompletableFuture.supplyAsync(() -> {
        sleep(1000);
        return 1;
    });

    CompletableFuture<Integer> future2 = CompletableFuture.supplyAsync(() -> {
        sleep(2000);
        return 2;
    });

    CompletableFuture<Integer> future3 = CompletableFuture.supplyAsync(() -> {
        sleep(3000);
        return 3;
    });

    //阻塞等待执行
    CompletableFuture.allOf(future1, future2, future3).join();
    Integer min = Stream.of(future1, future2, future3).map(item -> {
        try {
            return item.get();
        } catch (InterruptedException | ExecutionException e) {
            e.printStackTrace();
        }
        return null;
    }).sorted().findFirst().orElseThrow(() -> new RuntimeException("没有最小值"));

    System.out.println("最小值为 " + min);

    currentDate("执行后");
}

执行结果

可以看出执行时间为3秒,就是执行时间最长的子线程的执行时间。

3. 那些年我双手插兜,不料踩入的坑!

1. join()和get()的异同:

join()和get()方法都是用来获取CompletableFuture异步之后的返回值

join()方法抛出的是uncheck异常(比如RuntimeException),不会强制开发者去捕获处理,会将异常包装成CompletionException异常 /CancellationException异常,但是本质原因还是代码内存在的真正的异常,

get()方法抛出的是经过检查的异常,ExecutionException, InterruptedException 需要用户手动try catch处理。

这个小细节可以省去很多显式处理异常的冗余代码,不过具体看应用场景啦!!

2. 捕获异常的坑

 如果你在遇到CompletableFuture抛出异常的时候一不注意可能会try catch错异常。

 话不多说,举个例子
  1. 先来个并发工具类
public class CompletableFutureEngine {

    private final static ExecutorService executorService = Executors.newFixedThreadPool(4);

    /**
     * 创建并行任务并执行
     *
     * @param list            数据源
     * @param function        API调用逻辑
     * @param exceptionHandle 异常处理逻辑
     * @return 处理结果列表
     */
    public static <S, T> List<T> parallelFutureJoin(Collection<S> list, Function<S, T> function, Consumer<Throwable> exceptionHandle) {

        List<CompletableFuture<T>> completableFutures = list.stream()
                .map(s -> CompletableFuture.supplyAsync(() -> function.apply(s)))
                .collect(Collectors.toList());

        List<T> results = new ArrayList<>();
        try {
            CompletableFuture.allOf(completableFutures.toArray(new CompletableFuture[0])).join();
            for (CompletableFuture<T> completableFuture : completableFutures) {
                results.add(completableFuture.get());
            }
        } catch (Exception e) {
            exceptionHandle.accept(e);
        }
        return results;
    }

}

写完之后你感觉很优雅,里面用了各种泛型,并且当程序执行报错的时候会回调exceptionHandle.accept(e),把错误传给调用测去做处理,此时你迫不及待去写个demo赶紧测试下:

public class EngineDemo {

    private static void sleep(long sleepTime) {
        try {
            Thread.sleep(sleepTime);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    private static void currentDate(String str) {
        // 创建一个Date对象,它包含了当前时间
        Date now = new Date();
        // 创建一个SimpleDateFormat对象,用于指定输出格式
        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        // 使用format方法将Date对象格式化为字符串
        String currentTime = dateFormat.format(now);
        // 打印当前时间
        System.out.println(str + "是: " + currentTime);
    }

    public static void main(String[] args) {
        currentDate("执行前");
        List<Integer> numList = CompletableFutureEngine.parallelFutureJoin(Arrays.asList(1, 3, 5),
            num -> {
                sleep(num * 1000);
                if (num == 1 || num == 3) {
                    throw new BusinessException("心别太大");
                }
                return num;
            }
            , e -> {
                if (e instanceof BusinessException) {
                    System.out.println("BusinessException =" + e.getMessage());
                } else {
                    System.out.println("Not BusinessException");
                }
            }
        );
        System.out.println(numList);
        currentDate("执行后");

    }
}

打印结果:

通过日志会发现子线程抛出异常BusinessException时,主线程捕获,但是发现捕获完之后抛给调用端处理,调用端判断是否是BusinessException,如果不是,打印日志Not BusinessException,恰好控制台Not BusinessException,说明调用端捕获的并不是BusinessException,那主线程抛出的是什么异常呢?我们可以去debug一下。

会发现捕获的异常为CompletionException,这个异常的里面有个cause属性,里面封装的就是子线程抛出的异常。我们可以再改下工具类试下!!

public class CompletableFutureEngine {

private final static ExecutorService executorService = Executors.newFixedThreadPool(4);

/**
 * 创建并行任务并执行
 *
 * @param list            数据源
 * @param function        API调用逻辑
 * @param exceptionHandle 异常处理逻辑
 * @return 处理结果列表
 */
public static <S, T> List<T> parallelFutureJoin(Collection<S> list, Function<S, T> function, Consumer<Throwable> exceptionHandle) {

    List<CompletableFuture<T>> completableFutures = list.stream()
            .map(s -> CompletableFuture.supplyAsync(() -> function.apply(s)))
            .collect(Collectors.toList());

    List<T> results = new ArrayList<>();
    try {
        CompletableFuture.allOf(completableFutures.toArray(new CompletableFuture[0])).join();
        for (CompletableFuture<T> completableFuture : completableFutures) {
            results.add(completableFuture.get());
        }
    } catch (Exception e) {
        exceptionHandle.accept(e.getCause());
    }
    return results;
}

}

在运行调用端,日志如下:

你会发现,调用端可以正常捕获异常了

从表面上看是CompletionException对BusinessException封装了一层,为了证实下,我们可以去看下源码:

当抛出异常之后这块会进行封装,你会发现这块ex就等于BusinessException,那我们接下来看看d.completeThrowable(ex)里面的执行逻辑,会发现在最后调用了CompletionException的构造方法把异常封装起来向外抛出。

大家感觉恍然大悟了没?是否感觉之前在用的时候没有注意这么多细节,

感觉对您有所启发的话,记得帮忙点赞,收藏加关注,并且分享给需要的小伙伴!!一起加油!!后面干货满满


### 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(); ```
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值