CompletableFuture

Runnable 无参,无返回值,无法抛出异常

 /**
 * @since   JDK1.0
 */
@FunctionalInterface
public interface Runnable {
    public abstract void run();
}

Callable 无参数。有返回值,可以抛出异常

 /**
 * @see Executor
 * @since 1.5
 * @author Doug Lea
 * @param <V> the result type of method {@code call}
 */
@FunctionalInterface
public interface Callable<V> {
    
    V call() throws Exception;
}

Callable 是一个泛型接口,里面只有一个 call() 方法,该方法可以返回泛型值 V ,使用起来就像这样:

Callable<String> callable = () -> {
    // Perform some computation
    Thread.sleep(2000);
    return "Return some result";
};

执行机制
先从执行机制上来看,Runnable 它既可以用在 Thread 类中,也可以用在 ExecutorService 类中配合线程池的使用;但是, Callable 只能在 ExecutorService 中使用

在这里插入图片描述
另外,在实际工作中,我们通常要使用线程池来管理线程,所以我们就来看看 ExecutorService 中是如何使用二者的
在这里插入图片描述
可以看到,使用ExecutorService 的 execute() 方法依旧得不到返回值,而 submit() 方法清一色的返回 Future 类型的返回值 那么
Future 到底是什么呢?
怎么通过它获取返回值呢?
我们带着这些疑问一点点来看

Future 接口 主要是对某个线程执行结果的进行处理
Future ,里面只有五个方法:
从方法名称上相信你已经能看出这些方法的作用

// 取消任务
boolean cancel(boolean mayInterruptIfRunning);

// 获取任务执行结果
V get() throws InterruptedException, ExecutionException;

// 获取任务执行结果,带有超时时间限制
V get(long timeout, TimeUnit unit) throws InterruptedException,                             ExecutionException,  TimeoutException;

// 判断任务是否已经取消
boolean isCancelled();

// 判断任务是否已经结束
boolean isDone();

铺垫了这么多,看到这你也许有些乱了,咱们赶紧看一个例子,演示一下几个方法的作用

public class FutureAndCallableExample {

   public static void main(String[] args) throws InterruptedException, ExecutionException {
      ExecutorService executorService = Executors.newSingleThreadExecutor();

      // 使用 Callable ,可以获取返回值
      Callable<String> callable = () -> {
         log.info("进入 Callable 的 call 方法");
         // 模拟子线程任务,在此睡眠 2s,
         // 小细节:由于 call 方法会抛出 Exception,这里不用像使用 Runnable 的run 方法那样 try/catch 了
         Thread.sleep(5000);
         return "Hello from Callable";
      };

      log.info("提交 Callable 到线程池");
      Future<String> future = executorService.submit(callable);

      log.info("主线程继续执行");

      log.info("主线程等待获取 Future 结果");
      // Future.get() blocks until the result is available
      String result = future.get();
      log.info("主线程获取到 Future 结果: {}", result);

      executorService.shutdown();
   }
}

如果你运行上述示例代码,主线程调用 future.get() 方法会阻塞自己,直到子任务完成。我们也可以使用 Future 方法提供的 isDone 方法,它可以用来检查 task 是否已经完成了,我们将上面程序做点小修改:

// 如果子线程没有结束,则睡眠 1s 重新检查
while(!future.isDone()) {
   System.out.println("Task is still not done...");
   Thread.sleep(1000);
}

FutureTask
同样先来看类结构

public interface RunnableFuture<V> extends Runnable, Future<V> {
    void run();
}

很神奇的一个接口,FutureTask 实现了 RunnableFuture 接口,而 RunnableFuture 接口又分别实现了 Runnable 和 Future 接口,所以可以推断出 FutureTask 具有这两种接口的特性:

有 Runnable 特性,所以可以用在 ExecutorService 中配合线程池使用
有 Future 特性,所以可以从中获取到执行结果

文章开头已经提到,实现 Runnable 接口形式创建的线程并不能获取到返回值,而实现 Callable 的才可以,所以 FutureTask 想要获取返回值,必定是和 Callable 有联系的,这个推断一点都没错,从构造方法中就可以看出来:

public FutureTask(Callable<V> callable) {
    if (callable == null)
        throw new NullPointerException();
    this.callable = callable;
    this.state = NEW;       // ensure visibility of callable
}

即便在 FutureTask 构造方法中传入的是 Runnable 形式的线程,该构造方法也会通过 Executors.callable 工厂方法将其转换为 Callable 类型:

public FutureTask(Runnable runnable, V result) {
    this.callable = Executors.callable(runnable, result);
    this.state = NEW;       // ensure visibility of callable
}

我是想说,使用 FutureTask 来演练烧水泡茶经典程序

洗水壶 1 分钟
烧开水 15 分钟

洗茶壶 1 分钟
洗茶杯 1 分钟
拿茶叶 2 分钟

public class MakeTeaExample {

   public static void main(String[] args) throws ExecutionException, InterruptedException {
      ExecutorService executorService = Executors.newFixedThreadPool(2);

      // 创建线程1的FutureTask
      FutureTask<String> ft1 = new FutureTask<String>(new T1Task());
      // 创建线程2的FutureTask
      FutureTask<String> ft2 = new FutureTask<String>(new T2Task());

      executorService.submit(ft1);
      executorService.submit(ft2);

      log.info(ft1.get() + ft2.get());
      log.info("开始泡茶");

      executorService.shutdown();
   }

   static class T1Task implements Callable<String> {

      @Override
      public String call() throws Exception {
         log.info("T1:洗水壶...");
         TimeUnit.SECONDS.sleep(1);

         log.info("T1:烧开水...");
         TimeUnit.SECONDS.sleep(15);

         return "T1:开水已备好";
      }
   }

   static class T2Task implements Callable<String> {
      @Override
      public String call() throws Exception {
         log.info("T2:洗茶壶...");
         TimeUnit.SECONDS.sleep(1);

         log.info("T2:洗茶杯...");
         TimeUnit.SECONDS.sleep(2);

         log.info("T2:拿茶叶...");
         TimeUnit.SECONDS.sleep(1);
         return "T2:福鼎白茶拿到了";
      }
   }
}

在这里插入图片描述

Future是Java5新加的一个接口,它提供了一种异步并行计算的功能。如果主线程需要执行一个很耗时的计算任务,我们就可以通过future把这个任务放到异步线程中执行。主线程继续处理其他任务,处理完成后,再通过Future获取计算结果。但是Future对于结果的获取,不是很友好,只能通过阻塞或者轮询的方式得到任务的结果阻塞的方式和异步编程的设计理念相违背,而轮询的方式会耗费无谓的CPU资源。另外,Future 的链接和整合都需要手动操作,因此,JDK8设计出CompletableFuture。CompletableFuture提供了一种观察者模式类似的机制,可以让任务执行完成后通知监听的一方。线程池也不用单独创建了。实际上,它CompletableFuture使用了默认线程池是ForkJoinPool.commonPool。
CompletableFuture提供了几十种方法,辅助我们的异步任务场景。这些方法包括创建异步任务、任务异步回调、多个任务组合处理等方面。

CompletableFuture
类结构
老规矩,先从类结构看起:
在这里插入图片描述
实现了 CompletionStage 接口
CompletionStage 这个接口还是挺陌生的,中文直译过来是【竣工阶段】,如果将烧水泡茶比喻成一项大工程,他们的竣工阶段体现是不一样的

在这里插入图片描述

单看线程1 或单看线程 2 就是一种串行关系,做完一步之后做下一步

一起看线程1 和 线程 2,它们彼此就是并行关系,两个线程做的事彼此独立互补干扰

泡茶就是线程1 和 线程 2 的汇总/组合,也就是线程 1 和 线程 2 都完成之后才能到这个阶段(当然也存在线程1 或 线程 2 任意一个线程竣工就可以开启下一阶段的场景)

所以,CompletionStage 接口的作用就做了这点事,所有函数都用于描述任务的时序关系,总结起来就是这个样子:
在这里插入图片描述
CompletableFuture 大约有50种不同处理串行,并行,组合以及处理错误的方法。别担心,我们按照相应的命名和作用进行分类,分分钟搞定50多种方法

串行关系
then 直译【然后】,也就是表示下一步,所以通常是一种串行关系体现, then 后面的单词(比如 run /apply/accept)就是上面说的函数式接口中的抽象方法名称了,它的作用和那几个函数式接口的作用是一样一样滴

CompletableFuture<Void> thenRun(Runnable action)
CompletableFuture<Void> thenRunAsync(Runnable action)
CompletableFuture<Void> thenRunAsync(Runnable action, Executor executor)
  
<U> CompletableFuture<U> thenApply(Function<? super T,? extends U> fn)
<U> CompletableFuture<U> thenApplyAsync(Function<? super T,? extends U> fn)
<U> CompletableFuture<U> thenApplyAsync(Function<? super T,? extends U> fn, Executor executor)
  
CompletableFuture<Void> thenAccept(Consumer<? super T> action) 
CompletableFuture<Void> thenAcceptAsync(Consumer<? super T> action)
CompletableFuture<Void> thenAcceptAsync(Consumer<? super T> action, Executor executor)
  
<U> CompletableFuture<U> thenCompose(Function<? super T, ? extends CompletionStage<U>> fn)  
<U> CompletableFuture<U> thenComposeAsync(Function<? super T, ? extends CompletionStage<U>> fn)
<U> CompletableFuture<U> thenComposeAsync(Function<? super T, ? extends CompletionStage<U>> fn, Executor executor)

聚合 And 关系
combine… with… 和 both…and… 都是要求两者都满足,也就是 and 的关系了

<U,V> CompletableFuture<V> thenCombine(CompletionStage<? extends U> other, BiFunction<? super T,? super U,? extends V> fn)
<U,V> CompletableFuture<V> thenCombineAsync(CompletionStage<? extends U> other, BiFunction<? super T,? super U,? extends V> fn)
<U,V> CompletableFuture<V> thenCombineAsync(CompletionStage<? extends U> other, BiFunction<? super T,? super U,? extends V> fn, Executor executor)

<U> CompletableFuture<Void> thenAcceptBoth(CompletionStage<? extends U> other, BiConsumer<? super T, ? super U> action)
<U> CompletableFuture<Void> thenAcceptBothAsync(CompletionStage<? extends U> other, BiConsumer<? super T, ? super U> action)
<U> CompletableFuture<Void> thenAcceptBothAsync( CompletionStage<? extends U> other, BiConsumer<? super T, ? super U> action, Executor executor)
  
CompletableFuture<Void> runAfterBoth(CompletionStage<?> other, Runnable action)
CompletableFuture<Void> runAfterBothAsync(CompletionStage<?> other, Runnable action)
CompletableFuture<Void> runAfterBothAsync(CompletionStage<?> other, Runnable action, Executor executor)

聚合 Or 关系
Either…or… 表示两者中的一个,自然也就是 Or 的体现了

<U> CompletableFuture<U> applyToEither(CompletionStage<? extends T> other, Function<? super T, U> fn)
<U> CompletableFuture<U> applyToEitherAsync(CompletionStage<? extends T> other, Function<? super T, U> fn)
<U> CompletableFuture<U> applyToEitherAsync(CompletionStage<? extends T> other, Function<? super T, U> fn, Executor executor)

CompletableFuture<Void> acceptEither(CompletionStage<? extends T> other, Consumer<? super T> action)
CompletableFuture<Void> acceptEitherAsync(CompletionStage<? extends T> other, Consumer<? super T> action)
CompletableFuture<Void> acceptEitherAsync(CompletionStage<? extends T> other, Consumer<? super T> action, Executor executor)

CompletableFuture<Void> runAfterEither(CompletionStage<?> other, Runnable action)
CompletableFuture<Void> runAfterEitherAsync(CompletionStage<?> other, Runnable action)
CompletableFuture<Void> runAfterEitherAsync(CompletionStage<?> other, Runnable action, Executor executor)

异常处理

CompletableFuture<T> exceptionally(Function<Throwable, ? extends T> fn)
CompletableFuture<T> exceptionallyAsync(Function<Throwable, ? extends T> fn)
CompletableFuture<T> exceptionallyAsync(Function<Throwable, ? extends T> fn, Executor executor)
        
CompletableFuture<T> whenComplete(BiConsumer<? super T, ? super Throwable> action)
CompletableFuture<T> whenCompleteAsync(BiConsumer<? super T, ? super Throwable> action)
CompletableFuture<T> whenCompleteAsync(BiConsumer<? super T, ? super Throwable> action, Executor executor)
        
       
<U> CompletableFuture<U> handle(BiFunction<? super T, Throwable, ? extends U> fn)
<U> CompletableFuture<U> handleAsync(BiFunction<? super T, Throwable, ? extends U> fn)
<U> CompletableFuture<U> handleAsync(BiFunction<? super T, Throwable, ? extends U> fn, Executor executor)

这个异常处理看着还挺吓人的,拿传统的 try/catch /finally 做个对比也就瞬间秒懂了

supplyAsync / runAsync

supplyAsync表示创建带返回值的异步任务的,相当于ExecutorService submit(Callable task) 方法,runAsync表示创建无返回值的异步任务

thenCompose

thenCompose 方法会在某个任务执行完成后,将该任务的执行结果作为方法入参然后执行指定的方法,该方法会返回一个新的CompletableFuture实例,如果该CompletableFuture实例的result不为null,则返回一个基于该result的新的CompletableFuture实例;如果该CompletableFuture实例为null,则,然后执行这个新任务,前后开启两个线程

 //thenCompose 的作用是前一个任务完成后在执行后面的任务,并将前一个任务的结果拿到;两者是不同的线程
        SmallTool.printTimeAndThread("小白进入餐厅");
        SmallTool.printTimeAndThread("小白点了番茄炒蛋加一分米饭");

        CompletableFuture<String> cf1 = CompletableFuture.supplyAsync(() -> {
            SmallTool.printTimeAndThread("厨师炒菜");
            SmallTool.sleepMills(10000L);
            return "番茄炒蛋";
        }).thenCompose(dish -> CompletableFuture.supplyAsync(() -> { 
                    SmallTool.printTimeAndThread("服务员打饭");
                    SmallTool.sleepMills(100L);
                    return dish + " + 米饭";
                }));
SmallTool.printTimeAndThread("打王者");
SmallTool.printTimeAndThread(String.format("%s,小白开始吃饭", cf1.join()));
        /*
        1631605994514	|	1	|	main	|	小白进入餐厅
        1631605994514	|	1	|	main	|	小白点了番茄炒蛋加一分米饭
        1631605994553	|	12	|	ForkJoinPool.commonPool-worker-9	|	厨师炒菜
        1631605994553	|	1	|	main	|	打王者
        1631605995564	|	13	|	ForkJoinPool.commonPool-worker-2	|	服务员打饭
        1631605996577	|	1	|	main	|	番茄炒蛋 + 米饭,小白开始吃饭
        */

thenApply / thenApplyAsync

thenApply 表示某个任务执行完成后执行的动作,使用一个线程完成
thenApplyAsync 表示某个任务执行完成后开启另一个线程执行下一个任务

CompletableFuture<String> cf1 = CompletableFuture.supplyAsync(()->{
                                SmallTool.printTimeAndThread("服务员结账500");
                                SmallTool.sleepMills(200L);
                                return "500";
                }).thenApply(money ->{
                    SmallTool.printTimeAndThread("服务员开发票");
                    SmallTool.sleepMills(100L);
                    return String.format("%s元发票",money);
                });
        //thenApply :服务员结完账后再进行开发票 始终都是一个线程

        //如果结账合和开发票的不是同一个服务员做
                CompletableFuture<String> cf1 = CompletableFuture.supplyAsync(()->{
                    SmallTool.printTimeAndThread("服务员A结账500");
                   SmallTool.sleepMills(2000L);
                   return "500";
               }).thenApplyAsync(money ->{
                    SmallTool.printTimeAndThread("服务员B开发票");
                    SmallTool.sleepMills(100L);
                   return String.format("%s元发票",money);
              });
//                SmallTool.printTimeAndThread("打电话");
//                SmallTool.printTimeAndThread(String.format("小白拿到%s,回家", cf1.join()));
//获取用户信息详情
	CompletableFuture<User> getUsersDetail(String userId) {
		return CompletableFuture.supplyAsync(() -> User.builder().id(12345L).name("日拱一兵").build());
	}

	//获取用户信用评级
	CompletableFuture<Double> getCreditRating(User user) {
		return CompletableFuture.supplyAsync(() -> CreditRating.builder().rating(7.5).build().getRating());
	}

这时,如果我们还是使用 thenApply() 方法来描述串行关系,返回的结果就会发生 CompletableFuture 的嵌套

CompletableFuture<CompletableFuture<Double>> result = completableFutureCompose.getUsersDetail(12345L)
		.thenApply(user -> completableFutureCompose.getCreditRating(user));

显然这不是我们想要的,如果想“拍平” 返回结果,thenCompose 方法就派上用场了

CompletableFuture<Double> result = completableFutureCompose.getUsersDetail(12345L)
				.thenCompose(user -> completableFutureCompose.getCreditRating(user));

这个和 Lambda 的map 和 flatMap 的道理是一样一样滴

另外还有和thenApply类似的方法

thenAccept/thenRun

thenAccept 需要参数但没有返回值
thenRun 不需要参数不需要返回值

thenCombine / thenAcceptBoth / runAfterBoth

这三个方法都是将两个CompletableFuture组合起来,只有这两个都正常执行完了才会执行某个任务,区别在于,thenCombine会将两个任务的执行结果作为方法入参传递到指定方法中,且该方法有返回值;thenAcceptBoth同样将两个任务的执行结果作为方法入参,但是无返回值;runAfterBoth没有入参,也没有返回值。注意两个任务中只要有一个执行异常,则将该异常信息作为指定任务的执行结果。测试用例如下

                CompletableFuture<String> cf2 = CompletableFuture.supplyAsync(() -> {
                    SmallTool.printTimeAndThread("厨师炒菜");
                    SmallTool.sleepMills(200L);
                    return "番茄炒蛋";
                }).thenCombine( CompletableFuture.supplyAsync(() -> {  //thenCombine 的作用是两个任务同时执行,拿到两个结果合并
                    SmallTool.printTimeAndThread("服务员蒸饭");
                    SmallTool.sleepMills(100L);
                    return "米饭";
                }),(dish,rice)->{
                    SmallTool.printTimeAndThread("服务员打饭");
                    SmallTool.sleepMills(100L);
                    return String.format("%s + %s 好了",dish,rice);
                });
                        /*
    输出:
    1631600751730	|	1	|	main	|	小白进入餐厅
    1631600751730	|	1	|	main	|	小白点了番茄炒蛋加一分米饭
    1631600751770	|	12	|	ForkJoinPool.commonPool-worker-9	|	厨师炒菜
    1631600751771	|	13	|	ForkJoinPool.commonPool-worker-2	|	服务员蒸饭
    1631600751771	|	1	|	main	|	打王者
    1631600752777	|	13	|	ForkJoinPool.commonPool-worker-2	|	服务员打饭
    1631600753794	|	1	|	main	|	番茄炒蛋 + 米饭 好了,小白开始吃饭
     */

applyToEither / acceptEither / runAfterEither

这三个方法都是将两个CompletableFuture组合起来,只要其中一个执行完了就会执行某个任务,其区别在于applyToEither会将已经执行完成的任务的执行结果作为方法入参,并有返回值;acceptEither同样将已经执行完成的任务的执行结果作为方法入参,但是没有返回值;runAfterEither没有方法入参,也没有返回值。注意两个任务中只要有一个执行异常,则将该异常信息作为指定任务的执行结果。

        //小白走出餐厅可以选择两路公交车,哪路先来就上哪路
                CompletableFuture<String> cf1 = CompletableFuture.supplyAsync(()->{
                    SmallTool.printTimeAndThread("700路公交正在赶来");
                    SmallTool.sleepMills(10000L);
                    return "700路来了";
                }).applyToEither(CompletableFuture.supplyAsync(()->{
                    SmallTool.printTimeAndThread("800公交正在赶来");
                    SmallTool.sleepMills(1000L);
                    return "800路来了";
                }),fristComeBus -> fristComeBus);
                SmallTool.printTimeAndThread("等公交");
                SmallTool.printTimeAndThread(String.format("%s,小白上车回家", cf1.join()));
         /*
        1631607276897	|	1	|	main	|	等公交
        1631607276897	|	13	|	ForkJoinPool.commonPool-worker-2	|	800公交正在赶来
        1631607276897	|	12	|	ForkJoinPool.commonPool-worker-9	|	700路公交正在赶来
        1631607277905	|	1	|	main	|	800路来了,小白上车回家
         */
         //另外该方法还可以进行打电话超过一分钟不接自动挂断

exceptionally

异常处理

//如果发生异常呢?
        CompletableFuture<String> cf1 = CompletableFuture.supplyAsync(() -> {
            SmallTool.printTimeAndThread("700路公交正在赶来");
            SmallTool.sleepMills(20000L);
            return "700来了";
        }).applyToEither(CompletableFuture.supplyAsync(() -> {
            SmallTool.printTimeAndThread("800路公交正在赶来");
            SmallTool.sleepMills(1000L);
            return "800来了";
        }), fristComeBus -> {
            if (fristComeBus.startsWith("800")) {        //startWith是否以800开头
                throw new RuntimeException("撞树了");
            }
            return fristComeBus;
        }).exceptionally(e -> {
            SmallTool.printTimeAndThread(e.getMessage());
            SmallTool.printTimeAndThread("小白叫出租车");
            return "出租车到了";
        });
        SmallTool.printTimeAndThread("等公交");
        SmallTool.printTimeAndThread(String.format("%s,小白上车回家", cf1.join()));
        /*
        1631608259119	|	13	|	ForkJoinPool.commonPool-worker-2	|	800路公交正在赶来
        1631608259119	|	12	|	ForkJoinPool.commonPool-worker-9	|	700路公交正在赶来
        1631608259119	|	1	|	main	|	等公交
        1631608260126	|	13	|	ForkJoinPool.commonPool-worker-2	|	java.lang.RuntimeException: 撞树了
        1631608260126	|	13	|	ForkJoinPool.commonPool-worker-2	|	小白叫出租车
        1631608260133	|	1	|	main	|	出租车到了,小白上车回家
         */

whenComplete

CompletableFuture的whenComplete方法表示,某个任务执行完成后,执行的回调方法,无返回值;并且whenComplete方法返回的CompletableFuture的result是上个任务的结果

handle

CompletableFuture的handle方法表示,某个任务执行完成后,执行回调方法,并且是有返回值的;并且handle方法返回的CompletableFuture的result是回调方法执行的结果

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值