两行代码搞定方法异步化

302 阅读7分钟

前言

  这周的一个清晨,刚开开心心的走到公司,我们帅气的leader就给我发了两个文档说让我看一下,面对leader的蜜汁微笑,我有了一丝丝不好的预感。

  下面进入正题,有文档当然是要改需求啦,大致看了一下,主要要改的地方是给新生分配学号的地方,从原来的一次给一个班级的所有学生分配学号变成了一次可以给多个院系的所有学生来分配学号,想着虽然这块以前不是我负责的,但是看起来也不是很难,就先开始了解系统原来分配学号的业务。

大概的流程是这样的(当然实际流程是要比这个要复杂一点的)。

在系统上试着走了一下这个流程,发现分配一个班的学生的学号需要六七秒。好家嚯,40个学生需要六七秒,那要是一次给多个专业的学生来分的话怎么着也得个几十秒吧,这不仅是用户体验极差的问题,接口也有超时的风险啊。 (有的张三可能会问为什么会这么慢,因为是要生成学号,这块是比较费时间的,但是我看了一下代码,一个方法写了300+行,放弃了,应为需求比较急,还是先实现功能,后续肯定会去优化的)

那么只能异步了,张三同学肯定首先可以想到的是用MQ,那么流程就会变成这个样子。 用MQ的话是完全OK的,但是要保证消息的可靠性(不了解如何保证的可以看我上一篇博客^_^),又要去写消费服务这无疑显得比较麻烦,为何不直接new一个线程去执行呢。

所以我想到使用CompletableFuture异步编排的方式来异步执行,那么流程图就会变成这个样子。

可以看到,省去了写消费服务的过程,下面来看具体代码。

未异步前:

未异步前:

是不是只用了两行代码呢。

下面来正式的介绍一下今天的主角CompletableFuture

CompletableFuture

Java 8 中, 新增加了一个包含 50 个方法左右的类–CompletableFuture,它提供了非常强大的 Future 的扩展功能,可以帮助我们简化异步编程的复杂性,并且提供了函数式编程的能力,可以通过回调的方式处理计算结果,也提供了转换和组合 CompletableFuture 的方法。

下面上API。

runAsync和supplyAsync

public static CompletableFuture<Void> runAsync(Runnable runnable)
public static CompletableFuture<Void> runAsync(Runnable runnable, Executor executor)
public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier)
public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier, Executor executor)
runAsync

启动一个异步线程。 executor为线程池,可以将任务全部提交如同一个线程来执行。没有指定Executor的方法会使用ForkJoinPool.commonPool() 作为它的线程池执行异步代码。如果指定线程池,则使用指定的线程池运行。以下所有的方法都类同。

CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
    //具体业务
    doSomething();
}, executor);
supplyAsync

启动一个带返回值的异步线程。

CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> {
     int i = 10 / 2;
     return i;
}, executor)

//获取返回值
Integer integer = future.get();

计算结果完成时的回调方法

当CompletableFuture的计算结果完成,或者抛出异常的时候,可以执行特定的Action,类似于ajax的回调。

public CompletableFuture<T> whenComplete(BiConsumer<? super T,? super Throwable> action)
public CompletableFuture<T> whenCompleteAsync(BiConsumer<? super T,? super Throwable> action)
public CompletableFuture<T> whenCompleteAsync(BiConsumer<? super T,? super Throwable> action, Executor executor)
public CompletableFuture<T> exceptionally(Function<Throwable,? extends T> fn)

whenComplete:是执行当前任务的线程执行继续执行 whenComplete 的任务。

whenCompleteAsync:是执行把 whenCompleteAsync 这个任务继续提交给线程池来进行执行。

CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> {
      int i = 10 / 0;
      return i;
}, executor).whenComplete((res,exception)->{
      //可以得到返回结果和异常
      System.out.println("结果"+res+"------"+"异常:"+exception);
});

thenApply 方法

当一个线程依赖另一个线程时,可以使用 thenApply 方法来把这两个线程串行化。

//T:上一个任务返回结果的类型
//U:当前任务的返回值类型
public <U> CompletableFuture<U> thenApply(Function<? super T,? extends U> fn)
public <U> CompletableFuture<U> thenApplyAsync(Function<? super T,? extends U> fn)
public <U> CompletableFuture<U> thenApplyAsync(Function<? super T,? extends U> fn, Executor executor)
CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> {
    int i = 10 / 2;
    return i;
}, executor).thenApply(i -> {
    return i + 10;
});

handle 方法

public <U> CompletionStage<U> handle(BiFunction<? super T, Throwable, ? extends U> fn);
public <U> CompletionStage<U> handleAsync(BiFunction<? super T, Throwable, ? extends U> fn);
public <U> CompletionStage<U> handleAsync(BiFunction<? super T, Throwable, ? extends U> fn,Executor executor);

handle 是执行任务完成时对结果的处理。 handle 方法和 thenApply 方法处理方式基本一样。不同的是 handle 是在任务完成后再执行,还可以处理异常的任务。thenApply 只可以执行正常的任务,任务出现异常则不执行 thenApply 方法。

 CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> {
    int i = 10 / 0;
    return i;
}, executor).handle((i, throwable) -> {
    if (throwable == null) {
        //没异常时返回结果
        i = i + 5;
    } else {
        //有异常时返回结果
        i = -1;
    }
    return i;
});

thenAccept 消费处理结果

public CompletionStage<Void> thenAccept(Consumer<? super T> action);
public CompletionStage<Void> thenAcceptAsync(Consumer<? super T> action);
public CompletionStage<Void> thenAcceptAsync(Consumer<? super T> action,Executor executor);

接收任务的处理结果,并消费处理,无返回结果。

CompletableFuture<Void> future = CompletableFuture.supplyAsync(() -> {
    int i = 10 / 2;
    return i;
}, executor).thenAccept(i -> {
    System.out.println(i);
});

thenRun 方法

public CompletionStage<Void> thenRun(Runnable action);
public CompletionStage<Void> thenRunAsync(Runnable action);
public CompletionStage<Void> thenRunAsync(Runnable action,Executor executor);

跟 thenAccept 方法不一样的是,不关心任务的处理结果。只要上面的任务执行完成,就开始执行 thenAccept 。

CompletableFuture<Void> future = CompletableFuture.supplyAsync(() -> {
    int i = 10 / 2;
    return i;
}, executor).thenRun(() -> {
    System.out.println("thenRun");
});

thenCombine 合并任务返回

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

thenCombine 会把两个 CompletionStage 的任务都执行完成后,把两个任务的结果一块交给 thenCombine 来处理后并返回。

CompletableFuture<Integer> f1 = CompletableFuture.supplyAsync(() -> 5, executor);
CompletableFuture<Integer> f2 = CompletableFuture.supplyAsync(() -> 10, executor);
CompletableFuture<Integer> f3 = f1.thenCombine(f2, (integer, integer2) -> {
    return integer + integer2;
});

thenAcceptBoth 合并任务不返回

public <U> CompletionStage<Void> thenAcceptBoth(CompletionStage<? extends U> other,BiConsumer<? super T, ? super U> action);
public <U> CompletionStage<Void> thenAcceptBothAsync(CompletionStage<? extends U> other,BiConsumer<? super T, ? super U> action);
public <U> CompletionStage<Void> thenAcceptBothAsync(CompletionStage<? extends U> other,BiConsumer<? super T, ? super U> action, Executor executor);

当两个CompletionStage都执行完成后,把结果一块交给thenAcceptBoth来进行处理,无返回值。

CompletableFuture<Integer> f1 = CompletableFuture.supplyAsync(() -> 5, executor);
CompletableFuture<Integer> f2 = CompletableFuture.supplyAsync(() -> 10, executor);
CompletableFuture<Void> f3 = f1.thenAcceptBoth(f2, (integer, integer2) -> {
    System.out.println(integer + integer2);
});

applyToEither 方法

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

两个CompletionStage,谁执行返回的结果快,我就用那个CompletionStage的结果进行下一步的转化操作并返回。

CompletableFuture<Integer> f1 = CompletableFuture.supplyAsync(() -> 5, executor);
CompletableFuture<Integer> f2 = CompletableFuture.supplyAsync(() -> 10, executor);
CompletableFuture<Integer> f3 = f1.applyToEither(f2, integer -> {
    return integer * 2;
});

acceptEither 方法

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

两个CompletionStage,谁执行返回的结果快,我就用那个CompletionStage的结果进行下一步的操作,无返回值。

CompletableFuture<Integer> f1 = CompletableFuture.supplyAsync(() -> 5, executor);
CompletableFuture<Integer> f2 = CompletableFuture.supplyAsync(() -> 10, executor);
CompletableFuture<Void> f3 = f1.acceptEither(f2, integer -> {
    System.out.println(integer);
});

runAfterEither 方法

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

两个CompletionStage,任何一个完成了都会执行下一步的操作。

CompletableFuture<Integer> f1 = CompletableFuture.supplyAsync(() -> 5, executor);
CompletableFuture<Integer> f2 = CompletableFuture.supplyAsync(() -> 10, executor);
CompletableFuture<Void> f3 = f1.runAfterEither(f2, () -> {
    System.out.println("一个已执行完毕");
});

runAfterBoth 方法

public CompletionStage<Void> runAfterBoth(CompletionStage<?> other,Runnable action);
public CompletionStage<Void> runAfterBothAsync(CompletionStage<?> other,Runnable action);
public CompletionStage<Void> runAfterBothAsync(CompletionStage<?> other,Runnable action,Executor executor);

两个CompletionStage,都完成了计算才会执行下一步的操作。

CompletableFuture<Integer> f1 = CompletableFuture.supplyAsync(() -> 5, executor);
CompletableFuture<Integer> f2 = CompletableFuture.supplyAsync(() -> 10, executor);
CompletableFuture<Void> f3 = f1.runAfterBoth(f2, () -> {
    System.out.println("两个都执行完毕");
});

thenCompose 方法

public <U> CompletableFuture<U> thenCompose(Function<? super T, ? extends CompletionStage<U>> fn);
public <U> CompletableFuture<U> thenComposeAsync(Function<? super T, ? extends CompletionStage<U>> fn) ;
public <U> CompletableFuture<U> thenComposeAsync(Function<? super T, ? extends CompletionStage<U>> fn, Executor executor) ;

thenCompose 方法允许你对两个 CompletionStage 进行流水线操作,第一个操作完成时,将其结果作为参数传递给第二个操作。

CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> 5, executor).
    thenCompose(i -> CompletableFuture.supplyAsync(() -> {
        return i + 10;
    }));

常见组合操作

这么多API在平常开发中是可以进行搭配各种组合的。下面列一下我常用的,当然根据业务的不同使用情况也会不同。

实例1:

CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> {
    int i = 10 / 0;
    return i;
}, executor).whenComplete((res, exception) -> {
    //可以得到返回结果和异常
    System.out.println("结果" + res + "------" + "异常:" + exception);
}).exceptionally(throwable -> {
    //可以拿到并处理异常,可返回默认值
    return 10;
});

Integer integer = future.get();

实例2:


public User getUser(){
    User user = new User();
    CompletableFuture<Void> f1 = CompletableFuture.runAsync(() -> {
       //获取名字
       String name = getName();
       user.setName(name);
    }, executor);

    CompletableFuture<Void> f2 = CompletableFuture.runAsync(() -> {
       //获取年龄
       String age = getAge();
       user.setAge(age);
    }, executor);

    CompletableFuture<Void> f2 = CompletableFuture.runAsync(() -> {
       //获取性别
       String gender = getGender();
       user.setGender(gender);
    }, executor);
    
	//在这里阻塞一下,等待所有线程执行完毕,不然可能还没set进去就直接返回user
    CompletableFuture.allOf(f1, f2,f3).get();

    return user;
}

完!

都看到着了,点个赞点个关注再走呗~

!