JKD8新特性之CompletableFuture详解

CompletableFuture实现了CompletionStage和Future两个接口,增加了异步回调、流式处理、多个Futrue组合处理的能力。

1.创建异步任务

1)submit

通常的线程池接口类ExecutorService中的execute方法返回值是void,即无法获取异步任务的执行状态;submit方法的返回值是Future,可以从此对象中获取任务执行的状态和结果,示例如下:
public void submitTest() throws ExecutionException, InterruptedException {
    //创建异步执行任务
    ExecutorService executorService = Executors.newSingleThreadExecutor();
    //声明Future,submit方法的参数为一个回调函数
    Future<String> cf = executorService.submit(() -> {
        System.out.println(Thread.currentThread() + "start,time:" + System.currentTimeMillis());
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        System.out.println(Thread.currentThread() + "exit,time:" + System.currentTimeMillis());
        return "test";
    });
    System.out.println("main thread start,time:" + System.currentTimeMillis());
    //等待子任务执行完成,如果已完成则直接返回结果
    //如果执行任务异常,则get方法会把之前捕获的异常抛出
    System.out.println("result:" + cf.get());
    System.out.println("main thread exit,time:" + System.currentTimeMillis());
}

可以看出子线程是异步执行的,主线程休眠等待子线程执行完成,子线程执行完成后唤醒主线程,主线程获取结果后退出。

2)supplyAsync / runAsync

supplyAsync表示创建带返回值的异步任务,相当于ExecutorService.submit(Callable<T> task)方法;runAsync表示创建无返回值的异步任务,相当于ExecutorService.submit(Runnable task)方法。这两个方法的效果跟submit是一样的,测试用例如下:
public void supplyAsync() throws ExecutionException, InterruptedException {
    //创建异步执行任务
    CompletableFuture<String> cf = CompletableFuture.supplyAsync(() -> {
        System.out.println(Thread.currentThread() + "start,time:" + System.currentTimeMillis());
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        System.out.println(Thread.currentThread() + "exit,time:" + System.currentTimeMillis());
        return "test";
    });
    System.out.println("main thread start,time:" + System.currentTimeMillis());
    //等待子任务执行完成,如果已完成则直接返回结果
    //如果执行任务异常,则get方法会把之前捕获的异常抛出
    System.out.println("result:" + cf.get());
    System.out.println("main thread exit,time:" + System.currentTimeMillis());
}
public void supplyRun() throws ExecutionException, InterruptedException {
    CompletableFuture cf = CompletableFuture.runAsync(() -> {
        System.out.println(Thread.currentThread() + "start,time:" + System.currentTimeMillis());
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        System.out.println(Thread.currentThread() + "exit,time:" + System.currentTimeMillis());
    });
    System.out.println("main thread start,time:" + System.currentTimeMillis());
    //等待子任务执行完成,如果已完成则直接返回结果
    //如果执行任务异常,则get方法会把之前捕获的异常抛出
    System.out.println("result:" + cf.get());
    System.out.println("main thread exit,time:" + System.currentTimeMillis());
}

这两个方法各有一个重载版本,可以指定执行异步任务的Executor实现,如果不指定,默认使用ForkJoinPool.commonPool(),如果机器是单核的,则默认使用ThreadPerTaskExecutor。

public void supplyAsync() throws ExecutionException, InterruptedException {
    ForkJoinPool pool = new ForkJoinPool();
    //创建异步执行任务
    CompletableFuture<String> cf = CompletableFuture.supplyAsync(() -> {
        System.out.println(Thread.currentThread() + "start,time:" + System.currentTimeMillis());
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        System.out.println(Thread.currentThread() + "exit,time:" + System.currentTimeMillis());
        return "test";
    },pool);
    System.out.println("main thread start,time:" + System.currentTimeMillis());
    //等待子任务执行完成,如果已完成则直接返回结果
    //如果执行任务异常,则get方法会把之前捕获的异常抛出
    System.out.println("result:" + cf.get());
    System.out.println("main thread exit,time:" + System.currentTimeMillis());
}
public void supplyRun() throws ExecutionException, InterruptedException {
    ExecutorService executorService= Executors.newSingleThreadExecutor();
    CompletableFuture cf = CompletableFuture.runAsync(() -> {
        System.out.println(Thread.currentThread() + "start,time:" + System.currentTimeMillis());
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        System.out.println(Thread.currentThread() + "exit,time:" + System.currentTimeMillis());
    },executorService);
    System.out.println("main thread start,time:" + System.currentTimeMillis());
    //等待子任务执行完成,如果已完成则直接返回结果
    //如果执行任务异常,则get方法会把之前捕获的异常抛出
    System.out.println("result:" + cf.get());
    System.out.println("main thread exit,time:" + System.currentTimeMillis());
}

2.异步回调

1)thenApply / thenApplyAsync

thenApply表示某个任务执行完成后的回调方法,会将该任务的返回值作为参数传入回调方法中,测试用例如下:
public void thenApply() throws ExecutionException, InterruptedException {
    ForkJoinPool pool = new ForkJoinPool();
    //创建异步执行任务
    CompletableFuture<String> cf = CompletableFuture.supplyAsync(() -> {
        System.out.println(Thread.currentThread() + "start job1,time:" + System.currentTimeMillis());
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        System.out.println(Thread.currentThread() + "exit job1,time:" + System.currentTimeMillis());
        return "test";
    },pool);
    //cf2使用cf的返回值作为参数,调用thenApply的回调函数
    //thenApply这里实际创建了一个新的CompletableFuture实例
    CompletableFuture<String> cf2 = cf.thenApply((result) -> {
        System.out.println(Thread.currentThread() + "start job2,time:" + System.currentTimeMillis());
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        System.out.println(Thread.currentThread() + "exit job2,time:" + System.currentTimeMillis());
        return "job2:test";
    });
    System.out.println("main thread start cf.get(),time:" + System.currentTimeMillis());
    //等待子任务执行完成,如果已完成则直接返回结果
    //如果执行任务异常,则get方法会把之前捕获的异常抛出
    System.out.println("result:" + cf.get());
    System.out.println("main thread start cf2.get(),time:" + System.currentTimeMillis());
    System.out.println("result2:" + cf2.get());
    System.out.println("main thread exit,time:" + System.currentTimeMillis());
}

从返回结果可以看出,job1执行结束后,执行job2的时候是将job1的返回结果传入了job2。thenApplyAsync和thenApply的区别在于,前者是将job2提交到线程池中执行,实际执行job2的可能是另一个线程;后者是由同一个线程执行完job1再执行job2,将上述测试用例中的thenApply改成thenApplyAsync后,执行结果如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Ak5o80nT-1674027490151)(https://s1.imagehub.cc/images/2022/07/27/thenApplyAsync.png “thenApplyAsync”)]

从返回结果可以看出,此时执行job1和job2的不再是同一个线程。thenApplyAsync有一个重载版本,可以指定异步任务执行的Executor实现,如果不指定。默认使用ForkJoinPool.commonPool()。
除了thenApply之外,还有thenAccept / thenRun两个方法,thenAccept 是将上一个任务的返回值作为参数,但是没有返回值;thenRun既没有入参,也没有返回值。这两个方法方法同理,每个方法都有一个Async结尾的方法,一个使用默认的Executor实现,另一个指定Executor实现;不带Async的方法由同一个线程执行job1和job2,带Async的会将任务提交到线程池,执行任务的线程与触发任务的线程不一定是同一个线程。

2)exceptionally

exceptionally方法是指定某个任务执行异常时的回调方法,会将抛出的异常作为参数传递到exceptionally的回调方法中,如果该任务正常执行会将exceptionally返回的CompletionStage的result作为该任务正常执行的结果,测试用例如下:
public void exceptionally() throws ExecutionException, InterruptedException {
    ForkJoinPool pool = new ForkJoinPool();
    //创建异步执行任务
    CompletableFuture<String> cf = CompletableFuture.supplyAsync(() -> {
        System.out.println(Thread.currentThread() + "start job1,time:" + System.currentTimeMillis());
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        if(true){
            throw new RuntimeException("test");
        }else{
            System.out.println(Thread.currentThread() + "exit job1,time:" + System.currentTimeMillis());
            return "test";
        }
    },pool);
    //cf2使用cf的返回值作为参数,调用thenApply的回调函数
    //cf抛出异常时则不会调用cf2的回调
    CompletableFuture<String> cf2 = cf.thenApplyAsync((result) -> {
        System.out.println(Thread.currentThread() + "start job2,time:" + System.currentTimeMillis());
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        System.out.println(Thread.currentThread() + "exit job2,time:" + System.currentTimeMillis());
        return "job2:test";
    });
    //cf执行异常时。会将抛出的异常作为参数传递给exceptionally的回调方法
    CompletableFuture<String> cf3 = cf.exceptionally((exception) -> {
        System.out.println(Thread.currentThread() + "start exception,time:" + System.currentTimeMillis());
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        System.out.println(Thread.currentThread() + "exit exception,time:" + System.currentTimeMillis());
        return "exception:test";
    });

    System.out.println("main thread start cf.get(),time:" + System.currentTimeMillis());
    //调用cf2会先调用cf,cf抛出异常后会进入cf3的回调
    System.out.println("result:" + cf2.get());
    System.out.println("main thread exit,time:" + System.currentTimeMillis());
}

3)whenComplete

whenComplete方法是当某个任务执行完成后的回调方法,会将执行结果或者抛出的异常作为参数传递给回调方法,如果是正常执行则异常为null,回调方法对应的CompletableFuture的result和该任务一致,如果该任务正常执行,则get方法返回执行结果,如果执行异常,则get方法抛出异常,测试用例如下:
public void whenComplete() throws ExecutionException, InterruptedException {
    ForkJoinPool pool = new ForkJoinPool();
    //创建异步执行任务
    CompletableFuture<String> cf = CompletableFuture.supplyAsync(() -> {
        System.out.println(Thread.currentThread() + "start job1,time:" + System.currentTimeMillis());
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        if(false){
            throw new RuntimeException("test");
        }else{
            System.out.println(Thread.currentThread() + "exit job1,time:" + System.currentTimeMillis());
            return "test";
        }
    },pool);
    //cf执行完会将执行结果和异常作为参数传入whenComplete的回调方法
    CompletableFuture<String> cf2 = cf.whenComplete((res,exception) -> {
        System.out.println(Thread.currentThread() + "start job2,time:" + System.currentTimeMillis());
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        if(Objects.nonNull(exception)){
            exception.printStackTrace();
        }else{
            System.out.println("success,result:" + res);
        }
        System.out.println(Thread.currentThread() + "exit job2,time:" + System.currentTimeMillis());
    });
    System.out.println("main thread start cf.get(),time:" + System.currentTimeMillis());
    //如果cf是正常执行的,cf2.get的结果就是cf执行的结果
    //如果cf是执行异常,则cf2.get会抛出异常
    System.out.println("result:" + cf2.get());
    System.out.println("main thread exit,time:" + System.currentTimeMillis());
}

4)handle

handle和whenComplete基本一致,区别在于handle的回调方法有返回值,且handle方法返回的CompletableFuture的result是回调方法的执行结果或者回调方法执行期间抛出的异常,与原始的CompletableFuture无关,测试用例如下:
public void handle() throws ExecutionException, InterruptedException {
    ForkJoinPool pool = new ForkJoinPool();
    //创建异步执行任务
    CompletableFuture<String> cf = CompletableFuture.supplyAsync(() -> {
        System.out.println(Thread.currentThread() + "start job1,time:" + System.currentTimeMillis());
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        if(false){
            throw new RuntimeException("test");
        }else{
            System.out.println(Thread.currentThread() + "exit job1,time:" + System.currentTimeMillis());
            return "test";
        }
    },pool);
    //cf执行完会将执行结果和异常作为参数传入whenComplete的回调方法
    CompletableFuture<String> cf2 = cf.handle((res,exception) -> {
        System.out.println(Thread.currentThread() + "start job2,time:" + System.currentTimeMillis());
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        System.out.println(Thread.currentThread() + "exit job2,time:" + System.currentTimeMillis());
        if(Objects.nonNull(exception)){
            return exception.getMessage();
        }else{
            return "success,result:" + res;
        }
    });
    System.out.println("main thread start cf.get(),time:" + System.currentTimeMillis());
    //如果cf是正常执行的,cf2.get的结果是cf2执行的结果,与cf无关
    System.out.println("result:" + cf2.get());
    System.out.println("main thread exit,time:" + System.currentTimeMillis());
}

3.组合处理

1)thenCombine / thenAcceptBoth / runAfterBoth

这三个方法都是将两个CompletableFuture组合起来,只有这两个都正常执行完了才会执行某个任务。区别在于,thenCombine会将两个任务的返回结果都作为参数传入回调方法,且该方法有返回值;thenAcceptBoth同样将两个任务的返回结果作为参数传入回调方法,但是没有返回值;runAfterBoth既没有入参也没有返回值。需要注意的是,两个任务中只要有一个执行异常,则会将该异常信息作为指定任务的执行结果,测试用例如下:
public void thenCombine() throws ExecutionException, InterruptedException {
    //创建异步执行任务1
    CompletableFuture<String> cf = CompletableFuture.supplyAsync(() -> {
        System.out.println(Thread.currentThread() + "start job1,time:" + System.currentTimeMillis());
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        System.out.println(Thread.currentThread() + "exit job1,time:" + System.currentTimeMillis());
        return "1";
    });
    //创建异步执行任务2
    CompletableFuture<String> cf2 = CompletableFuture.supplyAsync(() -> {
        System.out.println(Thread.currentThread() + "start job2,time:" + System.currentTimeMillis());
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        System.out.println(Thread.currentThread() + "exit job2,time:" + System.currentTimeMillis());
        return "2";
    });
    //cf和cf2都执行完后,将执行结果作为参数传入cf3的回调方法
    CompletableFuture<String> cf3 = cf.thenCombine(cf2,(a,b) -> {
        System.out.println(Thread.currentThread() + "start job3,time:" + System.currentTimeMillis());
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        System.out.println(Thread.currentThread() + "exit job3,time:" + System.currentTimeMillis());
        return a + b + "3";
    });
    System.out.println("main thread start,time:" + System.currentTimeMillis());
    //等待子任务执行完成,如果已完成则直接返回结果
    //如果执行任务异常,则get方法会把之前捕获的异常抛出
    System.out.println("result:" + cf3.get());
    System.out.println("main thread exit,time:" + System.currentTimeMillis());
}

2)applyToEither / acceptEither / runAfterEither

这三个方法也是将两个CompletableFuture组合起来,只要其中任意一个执行完成,就会执行某个任务,只会将执行完的任务的返回结果作为参数传入回调函数。区别与apply / accept / run 的区别相同,测试用例如下:
public void applyToEither() throws ExecutionException, InterruptedException {
    //创建异步执行任务1
    CompletableFuture<String> cf = CompletableFuture.supplyAsync(() -> {
        System.out.println(Thread.currentThread() + "start job1,time:" + System.currentTimeMillis());
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        System.out.println(Thread.currentThread() + "exit job1,time:" + System.currentTimeMillis());
        return "1";
    });
    //创建异步执行任务2
    CompletableFuture<String> cf2 = CompletableFuture.supplyAsync(() -> {
        System.out.println(Thread.currentThread() + "start job2,time:" + System.currentTimeMillis());
        try {
            //cf2睡久一点方便看出执行顺序
            Thread.sleep(10000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        System.out.println(Thread.currentThread() + "exit job2,time:" + System.currentTimeMillis());
        return "2";
    });
    //cf或cf2任意一个执行完后,只将该执行完的任务的返回结果作为参数传入回调函数
    CompletableFuture<String> cf3 = cf.applyToEither(cf2,(res) -> {
        System.out.println(Thread.currentThread() + "start job3,time:" + System.currentTimeMillis());
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        System.out.println(Thread.currentThread() + "exit job3,time:" + System.currentTimeMillis());
        return res + "3";
    });
    System.out.println("main thread start,time:" + System.currentTimeMillis());
    //等待子任务执行完成,如果已完成则直接返回结果
    //如果执行任务异常,则get方法会把之前捕获的异常抛出
    System.out.println("result:" + cf3.get());
    System.out.println("main thread exit,time:" + System.currentTimeMillis());
}

3)thenCompose

thenCompose方法会在执行完某个任务后,将该任务的执行结果作为参数传入指定的方法,该方法会返回一个新的CompletableFuture实例,测试用例如下:
public void thenCompose() throws ExecutionException, InterruptedException {
    //创建异步执行任务
    CompletableFuture<String> cf = CompletableFuture.supplyAsync(() -> {
        System.out.println(Thread.currentThread() + "start job1,time:" + System.currentTimeMillis());
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        System.out.println(Thread.currentThread() + "exit job1,time:" + System.currentTimeMillis());
        return "1";
    });
    CompletableFuture<String> cf2 = cf.thenCompose((res) -> {
        System.out.println(Thread.currentThread() + "start job2,time:" + System.currentTimeMillis());
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        System.out.println(Thread.currentThread() + "exit job2,time:" + System.currentTimeMillis());
        return CompletableFuture.supplyAsync(() -> {
            System.out.println(Thread.currentThread() + "start job3,time:" + System.currentTimeMillis());
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            System.out.println(Thread.currentThread() + "exit job3,time:" + System.currentTimeMillis());
            return "job3:test";
        });
    });
    System.out.println("main thread start,time:" + System.currentTimeMillis());
    System.out.println("result:" + cf2.get());
    System.out.println("main thread exit,time:" + System.currentTimeMillis());
}

4)allOf / anyOf

allOf返回的CompletableFuture是多个任务执行完之后才会运行,只要有一个任务执行异常,回调函数就会抛出异常,如果所有任务都正常执行,get方法返回结果为null;anyOf返回的CompletableFuture是只要一个任务执行完成后就会运行,get方法返回的是已经执行完成的任务的返回结果。这两个方法的参数为不定数组,可以传入任意数量的CompletableFuture实例。测试用例如下:
public void allOf() throws ExecutionException, InterruptedException {
    CompletableFuture<String> cf = CompletableFuture.supplyAsync(() -> {
        System.out.println(Thread.currentThread() + "start job1,time:" + System.currentTimeMillis());
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        System.out.println(Thread.currentThread() + "exit job1,time:" + System.currentTimeMillis());
        return "1";
    });
    CompletableFuture<String> cf2 = CompletableFuture.supplyAsync(() -> {
        System.out.println(Thread.currentThread() + "start job2,time:" + System.currentTimeMillis());
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        System.out.println(Thread.currentThread() + "exit job2,time:" + System.currentTimeMillis());
        return "2";
    });
    //allof等待所有任务执行完成才执行,如果有一个任务异常终止,则cf3.get时会抛出异常,都是正常执行,cf3.get返回null
    //anyOf是只有一个任务执行完成,无论是正常执行或者执行异常,都会执行cf3,cf3.get的结果就是已执行完成的任务的执行结果
    CompletableFuture cf3 = CompletableFuture.allOf(cf,cf2).whenComplete((res,exception) -> {
        if(Objects.nonNull(exception)){
            exception.printStackTrace();
        }else{
            System.out.println("success,result:"+res);
        }
    });
    System.out.println("main thread start,time:" + System.currentTimeMillis());
    System.out.println("result:" + cf3.get());
    System.out.println("main thread exit,time:" + System.currentTimeMillis());
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值