JAVA高并发——CompletableFuture

CompletableFuture是Java 8新增的一个超大型工具类。为什么说它大呢?因为它实现了Future接口,而更重要的是,它也实现了CompletionStage接口。CompletionStage接口也是Java 8中新增的,它拥有多达40种方法!是的,你没有看错,这看起来完全不符合设计中所谓的“单方法接口”原则,但是在这里,它就这么存在了。这个接口拥有如此众多的方法,是为函数式编程中的流式调用准备的。通过CompletionStage接口,我们可以在一个执行结果上进行多次流式调用,以得到最终结果。比如,你可以在一个CompletionStage接口上进行如下调用:
在这里插入图片描述
以上一连串的调用会依次执行。

1、完成了就通知我

CompletableFuture和Future一样,可以作为函数调用的契约。向CompletableFuture请求一个数据,如果数据还没有准备好,请求线程就会等待。让人惊喜的是,我们可以手动设置CompletableFuture的完成状态:

import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;

/**
 * @title AskThread
 * @description CompletableFuture
 * @author: yangyongbing
 * @date: 2024/2/27 8:28
 */
public class AskThread implements Runnable{
    CompletableFuture<Integer> re=null;

    public AskThread(CompletableFuture<Integer> re) {
        this.re = re;
    }

    @Override
    public void run() {
        int myRe=0;
        try {
            myRe=re.get()*re.get();
        } catch (InterruptedException | ExecutionException e) {
            e.printStackTrace();
        }
        System.out.println(myRe);
    }

    public static void main(String[] args) throws InterruptedException {
        final CompletableFuture<Integer> future=new CompletableFuture<>();
        new Thread(new AskThread(future)).start();
        // 模拟长时间的计算过程
        Thread.sleep(1000);
        // 告知完成状态
        future.complete(60);
    }
}

在这里插入图片描述
上述代码在第1~17行定义了一个AskThread线程。它接收一个CompletableFuture作为构造函数,它的任务是计算CompletableFuture表示的数字的平方,并将其打印。

代码第20行创建一个CompletableFuture对象实例。第21行,我们将这个对象实例传递给AskThread线程,并启动这个线程。此时,AskThread在执行到第12行代码时会阻塞,因为CompletableFuture中根本没有它所需要的数据,整个CompletableFuture处于未完成状态。第23行用于模拟长时间的计算过程。当计算完成后,可以将最终数据载入CompletableFuture,并标记为完成状态(第25行)。

当第25行代码执行后,表示CompletableFuture已经完成,因此AskThread就可以继续执行了。

2、异步执行任务

通过CompletableFuture提供的进一步封装,我们很容易实现Future模式那样的异步调用,比如:
在这里插入图片描述
上述代码第11~12行使用CompletableFuture.supplyAsync()方法构造一个CompletableFuture实例,在supplyAsync()方法中,它会在一个新的线程中,执行传入的参数。在这里,它会执行calc()方法。而calc()方法的执行可能是比较慢的,但是这不影响CompletableFuture实例的构造速度,因此supplyAsync()方法会立即返回,它返回的CompletableFuture对象实例就可以作为这次调用的契约,将来在任何场合都可以用于获得最终的计算结果。代码第13行试图获得calc()方法的计算结果,如果当前计算没有完成,则调用get()方法的线程就会等待。

在CompletableFuture中,类似的工厂方法如下:
在这里插入图片描述
其中supplyAsync()方法用于那些需要返回值的场景,比如计算某个数据。runAsync()方法用于没有返回值的场景,比如仅仅简单地执行某一个异步动作。

在这两对方法中,都有一个方法可以接收Executor参数。这就使得我们可以让Supplier或者Runnable在指定的线程池中工作。如果不指定线程池,则在默认的系统公共的ForkJoinPool.common线程池中执行。

注意:在Java 8中,新增了ForkJoinPool.commonPool()方法,可以获得一个公共的ForkJoin线程池。这个公共线程池中的所有线程都是Daemon线程,这意味着如果主线程退出,那么这些线程无论是否执行完毕,都会退出系统。

3、流式调用

在前文中我已经提到,CompletionStage的40个方法是为函数式编程做准备的。下面就让我们看一下,如何使用这些方法进行函数式的流式API调用:
在这里插入图片描述
上述代码使用supplyAsync()方法执行一个异步任务。接着连续使用流式调用对任务的处理结果进行再加工,直到最后输出结果。

上述代码使用supplyAsync()方法执行一个异步任务。接着连续使用流式调用对任务的处理结果进行再加工,直到最后输出结果。

在第15行执行CompletableFuture.get()方法,目的是等待calc()方法执行完成。由于CompletableFuture异步执行,如果不进行等待,那么主函数不等calc()方法执行完毕就会退出,随着主线程的结束,所有的Daemon线程都会立即退出,从而会导致calc()方法无法正常完成。

4、CompletableFuture中的异常处理

如果CompletableFuture在执行过程中遇到异常,那么我们可以用函数式编程的风格来优雅地处理这些异常。CompletableFuture提供了一个异常处理方法exceptionally():
在这里插入图片描述
在上述代码中,第8行对当前的CompletableFuture进行异常处理。如果没有异常发生,则CompletableFuture会返回原有的结果。如果遇到了异常,就可以在exceptionally()方法中处理异常,并返回一个默认的值。在上例中,我们忽略了异常堆栈,只是简单地打印异常的信息。

执行上述方法,将得到如下输出:
在这里插入图片描述

5、组合多个CompletableFuture

CompletableFuture还允许你将多个CompletableFuture进行组合。一种组合方法是使用thenCompose()方法,它的签名如下:
在这里插入图片描述
在执行完成后,CompletableFuture可以将执行结果通过Function接口传递给下一个CompletionStage实例进行处理(Function接口返回新的CompletionStage实例):
在这里插入图片描述
上述代码第8行,将处理后的结果传递给thenCompose()方法,并进一步传递给后续新生成的CompletableFuture实例。以上代码的输出如下:
在这里插入图片描述
另外一种组合多个CompletableFuture的方法是thenCombine(),它的签名如下:
在这里插入图片描述
thenCombine()方法首先完成当前CompletableFuture和other的执行,接着将这两者的执行结果传递给BiFunction(该接口接收两个参数,并有一个返回值),并返回代表BiFunction实例的CompletableFuture对象。
在这里插入图片描述
在上述代码中,首先生成两个CompletableFuture实例(第6~7行),接着使用thenCombine()方法组合这两个CompletableFuture,将两者的执行结果进行累加(由第9行的(i, j)->(i+j)实现),并将累加结果转为字符串输出。上述代码的输出是:
在这里插入图片描述

6、支持timeout的CompletableFuture

在JDK 9以后CompletableFuture增加了timeout功能。如果一个任务在给定时间内没有完成,则直接抛出异常。例如:
在这里插入图片描述
本例中CompletableFuture.orTimeout()方法指定Future的执行时间不能超过1秒,如果超过1秒,则抛出TimeoutException异常。在第10行的异常处理中,得到的异常正是由orTimeout()方法抛出的。本例的输出如下:
在这里插入图片描述

  • 20
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值