CompletableFuture小结

CompletableFuture常用API整理

一 简介

​ 在读Apache Ratis源码时,发现其中大量使用CompletableFuture以及Functional Inteface,故抽身整理本文,特此注明:本文为个人初学总结,会存在很多问题,非喜勿喷。初始学习视频链接为Youtube

二 Functional Interface

Supplier<T>:    T get();
Predicate<T>:   boolean test(T);
Function<T, R>: R apply(T);
Consumer<T>:    void accept(T)

这四个Functioanl Interface可以表示绝大多数方法,出去Predicate不谈,其余三个分别为

T get() : 没有形参,有返回值

R apply(T): 有形参,有返回值

void accept(T): 有形参,没有返回值

为什么说可以表示绝大多数方法,因为还有一种可能性,就是"没有形参,没有返回值",没错,就是Runnable接口,即void run()方法。

三 CompletableFuture

其中Precdicate接口可以忽略,另外四个(包含run()方法)在CompletableFuture中都能找到对应,分别为

supplyAsync();
runAsync();
thenApply();
thenAccept();

3.1 supplyAsync(Supplier) / runAsync(Runnable)

这两个方法是CompletableFuture的静态方法,用来构造CompletableFutre对象,区别也很明显

  1. runAsync()方法返回CompletableFuture对象
  2. supplyAsync()方法返回CompletableFutre对象,其中T为用户指定类型
public class RunAsyncDemo {
    public static void main(String[] args) throws InterruptedException {
        CompletableFuture<Void> voidFuture = CompletableFuture.runAsync(RunAsyncDemo::doRun);
        TimeUnit.MILLISECONDS.sleep(100);
    }

    private static void doRun(){
        System.out.println("no parameter, no return value");
    }
}

对应输出为

no parameter, no return value
public class SupplyAsync {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        CompletableFuture<String> stringFuture = CompletableFuture.supplyAsync(SupplyAsync::doSupply);
        System.out.println(stringFuture.get());
    }
    
    private static String doSupply(){
        return "no parameter, has return value";
    }
}

对应输出为

no parameter, has return value

这里需要先解释的是,正如方法名runAsync/supplyAsync,这里是将Runnable/Supplier对象交给CompletableFuture对象,异步执行Runnable/Supplier方法体,所以在构造CompletableFuture之后,想要看到异步执行的效果,要么手动设置休眠时间,要么通过get()方法阻塞等待,避免CompletableFuture还没有执行,main thread执行完毕整体推出。据说CompletableFuture中,有一个Fork-Join线程池,用来执行提交的任务,但是目前还没有深究;另外,在调用CompletableFuture.runAsync()/supplyAsync()方法时,可以传递第二个参数,为用户指定线程池,即CompletableFuture对象不再使用内置的Fork-Join线程池异步执行任务,而是使用用户传递过来的实参线程池。

public class SupplyAsync {
    private static ExecutorService es = Executors.newFixedThreadPool(5);
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        CompletableFuture<String> selfThreadPool = CompletableFuture.supplyAsync(SupplyAsync::doSupply, es);
        System.out.println("selfThreadPool's name is: " + selfThreadPool.get());

        CompletableFuture<String> defaultThreadPool = CompletableFuture.supplyAsync(SupplyAsync::doSupply);
        System.out.println("defaultThreadPool's name is: " + defaultThreadPool.get());
        es.shutdown();
    }

    private static String doSupply(){
        return Thread.currentThread().getName();
    }
}

对应输出为

selfThreadPool's name is: pool-1-thread-1
defaultThreadPool's name is: ForkJoinPool.commonPool-worker-51

3.2 thenRun() / thenAccept() / thenApply()

首先明确,将CompletableFuture.then…()方法统称为thenMethods()方法,初识CompletableFuture,真的会有点奇怪,CompletableFuture.thenMethods()方法均返回一个CompletableFuture对象,有点像Builder模式,但实际上有着很大的区别,在使用静态内部类Builder时,每一个setter()方法返回的都是同一个Builder对象,即this,但是,每次在调用CompletableFuture.thenMethods()方法后,返回的都是一个新的CompletableFuture对象,这点需要分清,类比我们在构建一个Pipeline,每一个stage都是一个单独的对象。

  1. thenRun()方法,调用方需为CompletableFuture对象,返回值为CompletableFuture对象
  2. thenAccept()方法,调用方需为CompletableFuture对象,返回值为CompletableFuture对象
  3. thenApply()方法,调用方需为CompletableFuture对象,返回值为CompletableFuture对象
public class ThenMethodsDemo {
    private static ExecutorService es = Executors.newFixedThreadPool(4);
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        CompletableFuture<Integer> future = create();
        CompletableFuture<Void> futureComplete = future.thenApply(data -> data * 3)
                .thenAccept(System.out::println)
                .thenRun(() -> {
                    System.out.println("thenRun() method thread name: " + Thread.currentThread().getName());
                });
        futureComplete.get();
        es.shutdown();
        System.out.println("main thread exit");
    }

    private static CompletableFuture<Integer> create(){
        return CompletableFuture.supplyAsync(() -> 2, es);
    }
}

对应输出为

6
thenRun() method thread name: main
main thread exit

这里还是通过调用futureComplete.get()方法阻塞等待futureComplete执行完毕,避免main thread退出过早。还有一点告诫未来的自己哈,这里的es对象并不是画蛇添足,thenRun()方法体中打印线程名也是点睛之笔。改造一下create()方法

private static CompletableFuture<Integer> create(){
    return CompletableFuture.supplyAsync(() -> {
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        return 2;
    }, es);
}

对应输出为

6
thenRun() method thread name: pool-1-thread-1
main thread exit

区别很明显,在create()方法中加上休眠时间,对象线程池名称会成为我们传入的es对象,也就是说执行thenRun()方法的线程并不再是main thread。

这里出现这个问题,其实就是对于thenRun() / thenRunAsync()方法的理解了,thenApply()、thenAccept()、thenRun()方法都有对应的Async的版本,通过度娘很难理清这两组方法的区别,于是查看了API,不幸的是,通过API说明依旧没有分清,如图

在这里插入图片描述

按照固有逻辑来讲,thenRun()方法应该是一个阻塞方法,调用方线程会一直等待thenRun()方法执行完成,但是改造完create()方法后,main方法并不是这样执行的,问题在哪里呢,在于thenRun()方法会判断调用者,即CompletableFuture对象是否已经执行完毕。若是,则使用main thread执行thenRun()方法;若否,则将thenRun()方法体丢尽线程池中异步执行,thenRun()方法会立即返回至main thread继续执行。我们可以继续改造ThenMethodDemo类

public class ThenMethodsDemo {
    private static ExecutorService es = Executors.newFixedThreadPool(4);
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        CompletableFuture<Integer> future = create();
        TimeUnit.MILLISECONDS.sleep(100);
        CompletableFuture<Void> futureComplete = future.thenApply(data -> data * 3)
                .thenAccept(System.out::println)
                .thenRun(() -> {
                    System.out.println("thenRun() method thread name: " + Thread.currentThread().getName());
                });
        es.shutdown();
        System.out.println("main thread exit");
    }

    private static CompletableFuture<Integer> create(){
        return CompletableFuture.supplyAsync(() -> 2, es);
    }
}

这里,我们做了三点改动

  1. create()方法不再休眠
  2. 在future构造完毕之后,休眠100ms,确保future能够执行完毕,即futre.get()方法会立即返回结果
  3. main thread中不再使用futureComplete.get()方法等待futureComplete执行完毕

为什么这么做呢,因为futureComplete在执行thenApply()方法时,会判断futureComplete已经完成,那么就使用main thread阻塞调用future.thenApply()方法,待该stage执行完毕后,调用thenAccept()方法,再次判断调用方CompletableFuture是否完成,实例为完成状态,那么阻塞调用thenAccept()方法,同理,阻塞调用thenRun()方法。故,实际上,整体除去create()方法体是异步执行,其余代码均为一个线程执行,所以在执行打印main thread exit时,所有的pipeline代码均已执行完毕。

综上,thenRun()方法和thenRunAsync()方法的区别如下:

  1. 当调用者对象还未执行完毕时,即future.get()方法无法立即返回,那么thenRun() / thenRunAysnc()方法执行代码一致,均为异步执行
  2. 当调用者对象已经执行完毕时,即future.get()方法可以立即返回,那么thenRun()方法会使用调用者线程阻塞执行代码,这也是跟thenRunAsync()方法执行方法体产生分歧的地方。

但是,这里还有一点疑惑,如上代码,为什么在异步执行thenRun()方法时,线程池可以使用到create()方法中传参的es对象,这点还心存疑惑。

3.3 thenRunAsync()/ thenAcceptAsync() / thenApplyAsync()

见3.2,目前还不知道有什么需要补充

3.4 get() / getNow() / Complete() / CompleteOnTimeout()

get()方法在已经贯穿始终,阻塞方法,等待调用者CompletableFuture对象执行完毕,但是对于这种没有timeout机制的方法,向来时不推荐使用的,因为可能要等到花儿都谢了。

由此提供了get((long timeout, TimeUnit unit)方法,可以指定阻塞等待时间,若等待时间结束,还未返回结果,则该方法会抛出TimeoutException;同时提供了getNow(T)方法,如果Completable对象没有执行完成,那么立即返回,返回值为用户传入实参

public class GetDemo {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        CompletableFuture<String> futureFinish = create()
                .thenApplyAsync(data -> data + 2)
                .thenApplyAsync(data -> {
                    try {
                        TimeUnit.SECONDS.sleep(3);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                    return data + "th day that I have learned the CompletableFuture";
                });
        String result = null;
        try {
            result = futureFinish.get(2, TimeUnit.SECONDS);
        } catch (TimeoutException e) {
            System.out.println("OOPS, timeout");
        }finally {
            if(result != null){
                System.out.println(result);
            }
        }

    }

    private static CompletableFuture<Integer> create(){
        return CompletableFuture.supplyAsync(() -> 2);
    }
}

对应输出结果

OOPS, timeout

将get()方法的2->4,对应输出结果

4th day that I have learned the CompletableFuture

getNow()方法不再演示,没什么好总结的。

接下来时complete(T value)方法,以及completeOnTimeout(T value, long timeout, TimeUnit unit)方法。

按Youtube视频所讲,CompletableFuture有三种状态,Pending、Resolved、Rejected,我理解的为对应为CompletableFuture正在执行中、执行完毕、执行异常,那么complete()方法就是将CompletableFuture从Pending状态手动改为Resolved状态,返回值即实参值

public class CompleteDemo {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        CompletableFuture<Integer> future = new CompletableFuture<>();
        CompletableFuture<Void> futureFinish = future.thenApplyAsync(data -> data * 3)
                .thenApplyAsync(data -> data + 1)
                        .thenAccept(System.out::println);

        TimeUnit.SECONDS.sleep(3);
        System.out.println("main thread exit");
    }
}

对应输出结果为

main thread exit

这里,无论等待多长时间,都不会有CompletableFuture的结果打印,原因在于,虽然我们创建了future对象,并构建了pipeline,但是future对象会一直处于Pending状态,并不能转变为Resolved状态,从而触发后续thenMethods()方法的执行,此时可以称此pipeline为inactive状态,这时就需要complete()方法派上用场了,我们可以手动将future的状态改为Resolved,即调用future.complete()方法

public class CompleteDemo {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        CompletableFuture<Integer> future = new CompletableFuture<>();
        CompletableFuture<Void> futureFinish = future.thenApplyAsync(data -> data * 3)
                .thenApplyAsync(data -> data + 1)
                        .thenAccept(System.out::println);
        //add this line
        future.complete(2);
        TimeUnit.SECONDS.sleep(3);
        System.out.println("main thread exit");
    }
}

对应输出为

7
main thread exit

CompleteOnTimeout(T value, long timeout, TimeUnit unit)方法就比较好理解了,等待对应的时间,如果Completable没有完成,那么手动将其状态转换为Resolved,并返回实参value。

3.5 completeExceptionally / exceptionally

刚刚提到过,CompletableFuture有三种状态,对应Pending、Resolved、Rejected,同时CompletableFuture维护了两个Channel,分别为data channel和exception channel,这也是跟Stream不同的地方,众所周知,在Java Stream中,不能很好的处理异常情况,但是CompletableFuture有效避免了此类问题,在thenMethod出错时,CompletableFuture会找pipeline中后续最近的exceptionnally()方法执行,并跳过其中的所有thenMethods()方法,如下图所示,圆括号中一系列thenMethods()方法都不会被执行,待执行完exceptionally()方法后,会跳回至pipeline中data channel中,继续执行后续thenMethod()方法

thenMethod -- thenMethod -- thenMethod   (thenMethods)  thenMethod -- thenMethod --------				thenMethod
									\              /								\			 /
									 exceptionally -- exceptionally -- exceptionally -- exceptionally
public class ExceptionallyDemo {
    public static void main(String[] args) throws InterruptedException {
        CompletableFuture<Void> future = create()
                .thenApply(ExceptionallyDemo::plus2)
                .thenApply(data -> data + 1)
                .thenApply(data -> data + 1)
                .exceptionally(ExceptionallyDemo::handleException)
                .thenAccept(System.out::println)
                .exceptionally(ExceptionallyDemo::handleException2);
        TimeUnit.SECONDS.sleep(1);
    }

    private static int plus2(int a){
        int random = new Random().nextInt(10);
        if(random % 2 == 0){
            return 2 * a;
        }else {
            throw new RuntimeException("OOPS, something wrong");
        }
    }

    private static int handleException(Throwable throwable){
        System.out.println("OOPS, I has caught the exception and resolved it");
        return -1;
    }

    private static Void handleException2(Throwable throwable){
        System.out.println("Sry, the exception is beyond my limit");
        return null;
    }

    private static CompletableFuture<Integer> create(){
        return CompletableFuture.supplyAsync(() -> 2);
    }
}

对应输出结果有两种,分别为

OOPS, I has caught the exception and resolved it
-1
6

到这里,就基本接近尾声了,还剩下一个CompleteExceptionlly()方法,这个方法比较有意思,它并不是将CompletableFuture的状态从Pending改为Resolved,而是从Pending改为Rejected,改造后的代码如下,可以看到completeExceptionally()方法将其状态改为Rejected后,触发了exceptionally()方法

public class ExceptionallyDemo {
    public static void main(String[] args) throws InterruptedException {
        CompletableFuture<Integer> future = new CompletableFuture<>();
        future
//                .thenApply(ExceptionallyDemo::plus2)
            .thenApply(data -> data + 1)
            .thenApply(data -> data + 1)
            .exceptionally(ExceptionallyDemo::handleException)
            .thenAccept(System.out::println)
            .exceptionally(ExceptionallyDemo::handleException2);

        future.completeExceptionally(new RuntimeException("OOPS, something wrong by myself"));

        TimeUnit.SECONDS.sleep(1);
    }

    private static int plus2(int a){
        int random = new Random().nextInt(10);
        if(random % 2 == 0){
            return 2 * a;
        }else {
            throw new RuntimeException("OOPS, something wrong");
        }
    }

    private static int handleException(Throwable throwable){
        System.out.println("ERROR: " + throwable);
        return -1;
    }

    private static Void handleException2(Throwable throwable){
        System.out.println("Sry, the exception is beyond my limit");
        return null;
    }

    private static CompletableFuture<Integer> create(){
        return CompletableFuture.supplyAsync(() -> 2);
    }
}

对应输出为

ERROR: java.util.concurrent.CompletionException: java.lang.RuntimeException: OOPS, something wrong by myself
-1
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值