《Java高并发程序设计》学习 --6.5 增强的Future:CompletableFuture

45 篇文章 0 订阅
CompletableFuture是Java 8新增的一个超大型工具类。它实现了Future接口,也实现了CompletionStage接口。CompletionStage接口拥有多达40种方法,是为了函数式编程中的流式调用准备的。通过CompletionStage提供的接口,可以在一个执行结果上进行多次流式调用,以此可以得到最终结果。比如,可以在一个CompletionStage上进行如下调用:
stage.thenApply(x -> square(x)).thenAccept(x -> System.out.println(x)).thenRun(() -> System.out.println())
这一连串的调用就会挨个执行。
1)完成了就通知我
CompletableFuture和Future一样,可以作为函数调用的契约。如果向CompletableFuture请求一个数据,如果数据还没有准备好,请求线程就会等待。通过CompletableFuture,可以手动设置CompletableFuture的完成状态。
public static 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(Exception e) {
        }
        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);
}
上述代码,定义了一个AskThread线程。它接收一个CompletableFuture作为其构造函数,它的任务是计算CompletableFuture表示的数字的平方,并将其打印。
代码中,创建一个CompletableFuture对象实例,将这个对象实例传递给AskThread线程,并启动这个线程。此时,AskThread在执行到myRe = re.get() * re.get();时会阻塞,因为CompletableFuture中根本没有它所需要的数据,整个CompletableFuture处于未完成状态。Thread.sleep(1000);模拟长时间的计算过程。当计算完成后,可以将最终数据载入CompletableFuture,并标记为完成状态。当future.complete(60);执行后,表示CompletableFuture已经完成,因此AskThread就可以继续执行了。
2)异步执行任务
通过CompletableFuture提供的进一步封装,很容易实现Future模式那样的异步调用。比如:
public static Integer calc(Integer para) {
    try {
        //模拟一个长时间执行
        Thread.sleep(1000);
    } catch(InterruptedException  e) {
    }
    return para*para;
}

public static void main(String[] args) throws InterruptedException, ExecutionException {
    final CompletableFuture<Integer> future = new CompletableFuture.supplyAsync(() -> calc(50));
    System.out.println(future.get());
}
上述代码中,使用CompletableFuture.supplyAsync()方法构造一个CompletableFuture实例,在supplyAsync函数中,它会在一个新的线程中,执行传入的参数。在这里,它会执行calc()方法。而calc()方法的执行可能是比较慢的,但是这不影响CompletableFuture实例的构造速度,因此supplyAsync()会立即返回,它返回CompletableFuture对象实例就可以作为这次调用的契约,在将来任何场合,用于获得最终的计算结果。最后一行代码试图获得calc()的计算结果,如果当前计算没有完成,则调用get()方法的线程就会等待。
在CompletableFuture中,类似的工厂方法有以下几个:
static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier);
static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier, Executor executor);
static CompletableFuture<Void> runAsync(Runnable runnable);
static CompletableFuture<Void> runAsync(Runnable runnable, Executor executor);
其中supplyAsync()方法用于那些需要有返回值的场景,比如计算某个数据等。而runAsync()方法用于没有返回值的场景,比如,仅仅是简单地执行某一个异步动作。
在这两对方法中,都有一个方法可以接收一个Executor参数。这就使我们让Supplier<U>或者Runnable在指定的线程池中工作。如果不指定,则在默认的系统公共的ForkJoinPool.common线程池中执行(在Java 8中,新增了ForkJoinPool.commonPool()方法。它可以获得一个公共ForkJoin线程池。这个公共的线程池中的所有线程都是Daemon线程。这意味着如果主线程退出,这些线程无论是否执行完毕,都会退出系统)。

3)流式调用
CompletionStage的约40个接口是为函数式编程做准备的。在这里,看一下如何使用这些接口进行函数式的流式API调用:
public static Integer calc(Integer para) {
    try {
    //模拟一个长时间执行
    Thread.sleep(1000);
    } catch(InterruptedException  e) {
    }
    return para*para;
}

public static void main(String[] args) throws InterruptedException, ExecutionException {
    final CompletableFuture<Void> future = CompletableFuture.supplyAsync(() -> calc(50))
        .thenApply((i)->Integer.toString(i))
        .thenApply((str)->"\"" +str + "\"")
        .thenAccept(System.out::println);
    future.get();
}
上述代码中,使用supplyAsync()函数执行一个异步任务。接着连续使用流式调用对任务的处理结果进行再加工,知道最后的结果输出。
这里,执行CompletableFuture.get()方法,目的是等待calc()函数执行完成。不过不进行这个等待调用,由于CompletableFuture异步执行的缘故,主函数不等calc()方法执行完毕就会退出,随着主线程的结束,所有的Daemon线程都会立即退出,从而导致calc()方法无法正常完成。

4)CompletableFuture中的异常处理
如果CompletableFuture在执行过程中遇到异常,我们可以用函数式编程的风格处理这些异常。CompletableFuture提供了一个异常处理方法
上述代码中,对当前的CompletableFuture进行异常处理。如果没有发生异常,则CompletableFuture就会返回原有的结果。如果遇到了异常,就可以在exceptionally()中处理异常,并返回一个默认的值。在上例中,忽略了异常堆栈,只是简单地打印异常的信息。
exceptionally():
    public static Integer calc(Integer para) {
        return para/0;
}

public static void main(String[] args) throws InterruptedException, ExecutionException {
    final CompletableFuture<Void> future = CompletableFuture.supplyAsync(() -> calc(50))
        .exceptionally(ex-> {
            System.out.println(ex.toString());
            return 0;
        })
        .thenApply((i)->Integer.toString(i))
        .thenApply((str)->"\"" +str + "\"")
        .thenAccept(System.out::println);
    future.get();
}
5)组合多个CompletableFuture
CompletableFuture还允许将多个CompletableFuture进行组合。一种方法是使用thenCompose(),它的签名如下:
public <U> CompletableFuture<U> thenCompose(Function<? super T, ? extends CompletionStage<U>> fn)
一个CompletableFuture可以在执行完成后,将执行结果通过Function传递给下一个CompletionStage进行处理(Function接口返回新的CompletionStage实例):
public static Integer calc(Integer para) {
    return para/2;
}

public static void main(String[] args) throws InterruptedException, ExecutionException {
    final CompletableFuture<Void> future = CompletableFuture.supplyAsync(() -> calc(50))
        .thenCompose((i)->CompletableFuture.supplyAsync(()->calc(i)))
        .thenApply((str)->"\"" +str + "\"")
        .thenAccept(System.out::println);
    future.get();
}
上述代码中,将处理后的结果传递给thenCompose(),并进一步传递给后续新生成的CompletableFuture实例,以上代码的输出如下:
“12”
另外一种组合 多个CompletableFuture的方法是thenCombine(),它的签名如下:
public <U,V> CompletableFuture<U> thenCombime
(CompletionStage<? extends U> other,
BiFunction<? super T, ? super U,? extends V> fn)
方法thenCombime()首先完成当前CompletableFuture和other的执行。接着,将这两者的执行结果传递给BiFunction(该接口接收两个参数,并有一个返回值),并返回代表BiFunction实例的CompletableFuture对象:
public static Integer calc(Integer para) {
    return para/2;
}
public static void main(String[] args) throws InterruptedException, ExecutionException {
    CompletableFuture<Integer> intFuture = CompletableFuture.supplyAsync(() -> calc(50));
    CompletableFuture<Integer> intFuture2 = CompletableFuture.supplyAsync(() -> calc25));
    CompletableFuture<Void> future = intFuture.thenCombine(intFuture2, (i,j) -> (i+j))
        .thenApply((str)->"\"" +str + "\"")
        .thenAccept(System.out::println);
    future.get();
}
上述代码中,首先生成两个CompletableFuture实例,接着使用thenCombine()组合这两个CompletableFuture,将两者的执行结果进行累加,并将其累加结果转为字符串,并输出,上述代码的输出是:
“37”
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值