Java CompletableFuture使用方式

 更新时间:2024年07月04日 10:02:37   作者:xindoo  
这篇文章主要介绍了Java CompletableFuture使用方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教

在我之前的文章IO密集型服务提升性能的三种方法中提到过,提升IO密集型应用性能有个方式就是异步编程,实现异步时一定会用到Future,使用多线程+Future我们可以让多个任务同时去执行,最后统一去获取执行结果,这样整体执行的时长就取决于最长的一个任务,比如如下代码:

public static void main(String[] args) throws ExecutionException, InterruptedException {
        ExecutorService executorService = Executors.newFixedThreadPool(2);
        System.out.println(new Date());
        Future<String> future1 = executorService.submit(() -> {
            Thread.sleep(5000);
            return new Date() + ":" + "thread1 result";
        });
        Future<String> future2 = executorService.submit(() -> {
            Thread.sleep(3000);
            return new Date() + ":" + "thread2 result";
        });
        try {
            System.out.println(future1.get());
            System.out.println(future2.get());
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        } catch (ExecutionException e) {
            throw new RuntimeException(e);
        }
    }

上面代码的运行时长是5s,如果两个任务串行执行的化,运行时间就会是8s,当多个任务多线程异步执行时,最终总的执行时长取决于最长任务的执行时长。Future在大部分异步变成情况下,已经能很好的满足我们异步变成的诉求了,但当我看到CompletableFuture这个东西的时候,才发现Future还是太简单,我还是太年轻。

CompletableFuture除了和Future一样可以获取执行结果外,它还**简化了异常、提供了手动设置结果的接口、链式操作、结果组合、回调,**接下来我们通过一些代码示例,来看下CompletableFuture这些特性如何吊打Future。

异常处理

在使用Future时,你只能在get()的时候抓取到异常,异常处理会和结果获取的逻辑混在一起,有些时候处理起来会比较麻烦,而CompletableFuture提供了exceptionallyhandle 两个api,可以很方便的添加异常处理逻辑,后续只需要直接使用get获取结果即可,代码示例如下:

    public static void main(String[] args) {
        ExecutorService executorService = Executors.newFixedThreadPool(2);
        CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
            // 故意抛出一个异常
            if (System.currentTimeMillis() % 2 == 0) {
                throw new RuntimeException("Exception occurred!");
            }
            return "Result";
        }, executorService);

        future = future.exceptionally(ex -> {
            // 对异常进行处理,并提供一个异常时的结果
            return "Default Value";
        });

        try {
            System.out.println(future.get()); // 输出 "Default Value"
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

exceptionally api让任务在运行过程中,如果抓到了Exception,就可以调用你给定的逻辑来处理异常。

与exceptionally就是handle方法,达到的效果一致,但使用略有不同,可以看出handle这个api是在任务执行完成后,讲exception和执行结果共同处理一次。

        future = future.handle((result, ex) -> {
            if (ex != null) {
                return "Default Value";
            }
            return result;
        });
        try {
            System.out.println(future.get()); // 输出 "Default Value"
        } catch (Exception e) {
            e.printStackTrace();
        }

手动设置结果

Future中,我们无法手动地完成一个任务或者设置任务的结果,这个任务的执行结果完全取决于任务的执行情况。

但在CompletableFuture,我们可以在任何时间点上手动设置结果或者标记任务为已完成。

举个例子,我要通过接口获取某个数据,但很要求时效性,如果超过1s我就不要接口调用结果了,而是返回一个默认值。

Future设置等待时间也可以实现类似的功能,但不是很直观, CompletableFuture就很简单直观了。

public static void main(String[] args) {
        ExecutorService executorService = Executors.newFixedThreadPool(2);
        CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
            try {
                // 随机sleep 0-2s
                Thread.sleep(ThreadLocalRandom.current().nextInt(2000));
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            return "Result";
        }, executorService);
        CompletableFuture<String> finalFuture = future;
        // 1s后手动完成
        executorService.submit(() -> {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            // 手动地完成这个 CompletableFuture,并设置计算结果
            finalFuture.complete("Default Value");
        });

        try {
            System.out.println(future.get()); // 概率性输出 "Default Value"
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

链式操作

链式操作意味着你可以将一个CompletableFuture的结果直接传递给另一个异步操作,而无需等待第一个操作完成。

这使得你可以更容易地编写复杂的并发逻辑,这种非常适合需要将多个接口结果串起来的流程,有点像langChain,我们直接看示例代码:

public static void main(String[] args) {
        CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
            // 第一个异步任务
            return "Hello";
        }).thenApplyAsync(result -> {
            // 第二个异步任务,依赖第一个任务的结果
            return result + " World";
        }).thenApplyAsync(result -> {
            // 第三个异步任务,依赖第二个任务的结果
            return result + "!";
        });
        try {
            System.out.println(future.get());
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        } catch (ExecutionException e) {
            throw new RuntimeException(e);
        }
    }

这个示例比较简单,在实际使用中,我们可以将三个任务替换成任何需要相互依赖的任务,你只需要定义每个任务的输入和输出,然后将它们连接在一起,就可以创建一个复杂的并发流程。

结果组合

CompletableFuture提供了几个方法来组合或合并多个CompletableFuture的结果。

最常用的可能是thenCombineallOf方法。

以下是thenCombine方法的使用示例:

CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> {
    return "Hello";
});

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

CompletableFuture<String> combinedFuture = future1.thenCombine(future2, (s1, s2) -> {
    return s1 + ", " + s2;
});

try {
    String result = combinedFuture.get();
    System.out.println(result); // 输出 "Hello, World!"
} catch (InterruptedException | ExecutionException e) {
    e.printStackTrace();
}

在上述代码中,我们首先创建了两个独立的CompletableFuture,然后使用thenCombine方法将它们的结果组合在一起。

以下是allOf方法的使用示例:

CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> {
    return "Hello";
});

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

CompletableFuture<Void> allFutures = CompletableFuture.allOf(future1, future2);

// 当所有的 CompletableFuture 都完成时,获取结果
allFutures.thenRun(() -> {
    try {
        String result1 = future1.get();
        String result2 = future2.get();
        System.out.println(result1 + ", " + result2); // 输出 "Hello, World!"
    } catch (InterruptedException | ExecutionException e) {
        e.printStackTrace();
    }
});

可以看出thenCombine和allOf都实现了类似的功能,但两者的侧重点不同,thenCombine实现的是对执行结果的聚合,而allOf实现的是对执行完状态的聚合,还是需要你去显式调用get()方法去获取结果的,所以你还是得根据具体需求来选择使用thenCombine还是allOf

回调

使用Future时,我们只能通过get()或者isDone()方法来获取到任务是否执行完成了,无法让任务执行完成后主动通知到我们,但CompletableFuture提供了一些回调方法,可以让任务执行完成后执行结果处理的任务或者执行一些通知的逻辑。

CompletableFuture提供了几个方法来设置回调,如thenApply, thenAccept, 和 thenRun

具体看代码示例:

public static void main(String[] args) {
        CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
            // 异步任务
            return "Hello, World!";
        });

        future.thenApply(result -> {
            // 当 future 完成时,这个回调会被调用。
            // result 参数是 future 的结果。
            // 这个回调的结果会被包装成一个新的 CompletableFuture。
            return result.length();
        }).thenAccept(length -> {
            // 当前面的 CompletableFuture 完成时,这个回调会被调用。
            // length 参数是前面 CompletableFuture 的结果。
            // 这个回调没有返回值。
            System.out.println(length); // 输出 "Hello, World!" 的长度
        }).thenRun(() -> {
            // 当前面的 CompletableFuture 完成时,这个回调会被调用。
            // 这个回调没有输入参数,也没有返回值。
            System.out.println("Done");
        });
    }

thenApply、thenAccept和thenRun都是在任务执行完成后被调起,但他们定位有所不同,thenApply处理的结果可以被包装成一个新的CompletableFuture,只后可以继续链式调用。

但thenAccept只能接受输入,无法提供输出,所以他的定位是任务的收尾,比如可以将结果输出。

而thenRun既没有输入也没有输出,所以它只是获取到了任务执行完的状态,任务的执行结果是获取不到的,所以它是最弱的。

总结

总结下本文,Future 尽管有用,但在功能上还是相对简单。

CompletableFuture不仅提供了获取执行结果的能力,它还增添了异常处理、手动完成、链式操作、结果组合和回调等强大功能。

  • 异常处理: 利用exceptionallyhandle方法,可以简化异常处理流程,使其更加直观和易于管理。
  • 手动设置结果: CompletableFuture允许我们在任意时间点手动设置结果,增加了灵活性。
  • 链式操作: 通过链式操作,我们可以将不同的异步操作以流水线的方式串联起来,编写复杂的并发逻辑变得更加简单。
  • 结果组合: thenCombineallOf方法能够将多个CompletableFuture的结果合并,为我们提供了更多处理异步操作结果的方式。
  • 回调: 提供了thenApply, thenAccept, 和 thenRun等方法,允许我们在任务完成后进行结果处理或其他逻辑操作。

通过这些特性,CompletableFuture显著地提升了异步编程的能力和灵活性。无论是简化异常处理,还是实现复杂的异步逻辑,CompletableFuture都能够帮助我们写出更清晰、更高效的代码。我相信通过本文的示例代码,你可以更深入地理解CompletableFuture的强大之处,并在你的项目中充分利用它来提升性能。

以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。

相关文章

  • SpringBoot使用Shiro实现动态加载权限详解流程

    SpringBoot使用Shiro实现动态加载权限详解流程

    本文小编将基于 SpringBoot 集成 Shiro 实现动态uri权限,由前端vue在页面配置uri,Java后端动态刷新权限,不用重启项目,以及在页面分配给用户 角色 、 按钮 、uri 权限后,后端动态分配权限,用户无需在页面重新登录才能获取最新权限,一切权限动态加载,灵活配置
    2022-07-07
  • Java前端Layer.open.btn验证无效解决方法

    Java前端Layer.open.btn验证无效解决方法

    在本篇文章里我们给大家整理了一篇关于Java前端Layer.open.btn验证无效解决方法以及实例代码,需要的朋友们可以参考学习下。
    2019-09-09
  • java监听器的实现和原理详解

    java监听器的实现和原理详解

    这篇文章主要给大家介绍了关于java监听器实现和原理的相关资料,文中通过示例代码介绍的非常详细,对大家学习或者使用java具有一定的参考学习价值,需要的朋友们下面来一起学习学习吧
    2019-08-08
  • java 全角半角字符转换的方法实例

    java 全角半角字符转换的方法实例

    这篇文章主要介绍了java 全角半角字符转换的方法,大家参考使用吧
    2013-11-11
  • 详解SpringBoot+Dubbo集成ELK实战

    详解SpringBoot+Dubbo集成ELK实战

    这篇文章主要介绍了详解SpringBoot+Dubbo集成ELK实战,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2019-10-10
  • mybatis mapper互相引用resultMap启动出错的解决

    mybatis mapper互相引用resultMap启动出错的解决

    这篇文章主要介绍了mybatis mapper互相引用resultMap启动出错的解决方案,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-08-08
  • 轻松掌握java装饰者模式

    轻松掌握java装饰者模式

    这篇文章主要帮助大家轻松掌握java装饰者模式,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2016-09-09
  • Java集合ArrayList与LinkedList详解

    Java集合ArrayList与LinkedList详解

    这篇文章主要介绍了Java集合ArrayList与LinkedList详解,对于ArrayList和LinkedList,他们都是List接口的一个实现类,并且我们知道他们的实现方式各不相同,例如ArrayList底层实现是一个数组
    2022-08-08
  • java阻塞队列BlockingQueue详细解读

    java阻塞队列BlockingQueue详细解读

    这篇文章主要介绍了java阻塞队列BlockingQueue详细解读,在新增的Concurrent包中,BlockingQueue很好的解决了多线程中,如何高效安全“传输”数据的问题,通过这些高效并且线程安全的队列类,为我们快速搭建高质量的多线程程序带来极大的便利,需要的朋友可以参考下
    2023-10-10
  • Java设计模式之职责链模式详解

    Java设计模式之职责链模式详解

    Java设计模式中有很多种类别,例如单例模式、装饰模式、观察者模式等。本文将为大家详细介绍其中的职责链模式,感兴趣的可以了解一下
    2021-12-12

最新评论