【转】Java8 新特性之--CompletableFuture

面试

CompletableFuture使用了空间换时间的思路,异步查询也可以使用FutureTask。

但是使用Future有个问题,就是在于返回获取异步结果的时候需要有等待状态,这个等待的状态是需要消耗时间进行堵塞的。

CompletableFuture只有在最后汇总的时候才进行堵塞,这时所有任务都已经完成了。

通过线程池性能稳定,也可以获取执行结果,并捕获异常。但是,在业务复杂的情况下,一个异步调用可能会依赖于另一个异步调用的执行结果。

CompletableFuture引入

Future是JDK5添加的类,用来描述一个异步计算的结果。可以使用isDone方法检查计算是否完成,或者使用get阻塞住调用线程,直到计算完成返回结果,也可以使用cancel方法停止任务的执行。

private static ExecutorService executorService = Executors.newFixedThreadPool(10);
public static void future1() throws Exception {
        Future<Integer> f = executorService.submit(() -> {
            // 长时间的异步计算
            // 然后返回计算结果
            System.out.println("A");
            return 100;
        });
//         while(!f.isDone()){
//            System.out.println("D");
//        }
        System.out.println("B");
        Integer i = f.get();
        System.out.println("C" + i);
        executorService.shutdown();
    }
    
结果:
B
A
C100

虽然Future以及相关使用方法提供了异步执行任务的能力,但是对于结果的获取却是很不方便,只能通过阻塞或者轮询的方式得到任务的结果。阻塞的方式显然和异步编程的初衷相违背,轮询的方式又会耗费无谓的CPU资源,而且也不能及时地得到计算结果,所以可以用观察者设计模式,当计算结果完成及时通知监听者。

在JDK8里面,就新增了CompletableFuture类,提供了非常强大的Future的扩展功能,可以帮助我们简化异步编程的复杂性,提供了函数式编程的能力,可以通过回调的方式处理计算结果,并且提供了转换和组合CompletableFuture的方法

CompletableFuture实现了FutureCompletionStage接口。CompletionStage接口定义了可与其他Future组合成异步计算契约。

public class CompletableFuture<T> implements Future<T>, CompletionStage<T>

CompletableFuture类实现Future接口,因此可以将其用作Future实现,但需要额外的完成实现逻辑。

例如:可以使用无构参构造函数创建此类的实例,然后使用complete方法完成。消费者可以使用get方法来阻塞当前线程,直到get()结果。

在下面的示例中,有一个创建CompletableFuture实例的方法,然后在另一个线程中计算并立即返回Future。

计算完成后,该方法通过将结果提供给完整方法来完成Future:

public static void main(String[] args) throws Exception{
    Future<String> future = calculateAsync();
    System.out.println(Thread.currentThread().getName() + " main");
    System.out.println(future.get());
}

public static Future<String> calculateAsync() {
    CompletableFuture<String> completableFuture = new CompletableFuture<>();
    Executors.newCachedThreadPool().execute(() -> {
        System.out.println(Thread.currentThread().getName() + " execute");
        String result = "Hello " + " World";
        completableFuture.complete(result);
    });
    return completableFuture;
}

结果:
main main
pool-1-thread-1 execute
Hello  World
Async后缀方法
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类中的API的大多数方法都有两个带有Async后缀的附加修饰。这些方法表示用于异步线程。

  • 没有Async后缀的方法使用调用线程运行下一个执行线程阶段。
  • 带有Async后缀的方法默认使用ForkJoinPool.commonPool()线程池的fork/join实现运算任务。
  • 带有Async后缀的方法并且传递Executor对象,由Executor任务去运行。

什么是ForkJoinPool

ForkJoinPool是ExecutorSerice的一个补充,而不是替代品。
JAVA8中CompeleteFuture、并发流等都是基于ForkJoinPool实现,默认并发数是CPU核数。

创建一个异步任务

CompletableFuture.completedFuture是一个静态辅助方法,用来返回一个已经计算好的CompletableFuture。

public static <U> CompletableFuture<U> completedFuture(U value)
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方法:它以Runnabel函数式接口类型为参数,所以CompletableFuture的计算结果为空。
  • supplyAsync方法:以Supplier<U>函数式接口类型为参数,所以CompletableFuture有返回值,且计算结果类型为U。

Async 结尾的方法都是可以异步执行的,如果指定了线程池,会在指定的线程池中执行,如果没有指定,默认会在ForkJoinPool.commonPool()中执行。

示例:

CompletableFuture<Void> future1 = CompletableFuture.runAsync(() -> System.out.println("runAsync"));
CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> "supplyAsync");

System.out.println(future1.get()); // 没有返回值 -》 null
System.out.println(future2.get());

结果:
runAsync
null
supplyAsync

计算完成时对结果的处理 whenComplete、exceptionally、handle

当CompletableFuture的计算结果完成,或者抛出异常的时候,我们可以执行特定的Action。

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)

参数类型为 BiConsumer<? super T, ? super Throwable> 会获取上一步计算的计算结果和异常信息。
以Async结尾的方法可能会使用其它的线程去执行,如果使用相同的线程池,也可能会被同一个线程选中执行。

public class ThreadUtil {
    public static void sleep(long ms) {
        try {
            Thread.sleep(ms);
        } catch (InterruptedException e) {
            e.printStackTrace();
            throw new RuntimeException(e.getMessage());
        }
    }
}

CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> {
    ThreadUtil.sleep(100);
    return 20;
}).whenCompleteAsync((v, e) -> {
    System.out.println("上一步的计算结果:" + v);
    System.out.println("上一步的异常信息:" + e);
});
System.out.println("A");
System.out.println(future.get());
System.out.println("B");

结果:
A
VV:20
EE:null
20
B

public CompletableFuture exceptionally(Function<Throwable,? extends T> fn)

该方法是对异常情况的处理,当函数异常时执行。

CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> {
    ThreadUtil.sleep(100);
    return 20/0;
}).whenCompleteAsync((v, e) -> {
    System.out.println("上一步的计算结果:" + v);
    System.out.println("上一步的异常信息:" + e);
}).exceptionally((e)->{
    System.out.println(e.getMessage()); // 上一步的异常信息
    return 30; // 默认的返回值
});
System.out.println("A");
System.out.println(future.get());
System.out.println("B");

结果:
A
上一步的计算结果:null
上一步的异常信息:java.util.concurrent.CompletionException: java.lang.ArithmeticException: / by zero
java.lang.ArithmeticException: / by zero
30
B
public <U> CompletableFuture<U> handle(BiFunction<? super T,Throwable,? extends U> fn)
public <U> CompletableFuture<U> handleAsync(BiFunction<? super T,Throwable,? extends U> fn)
public <U> CompletableFuture<U> handleAsync(BiFunction<? super T,Throwable,? extends U> fn, Executor executor)

handle 方法和whenComplete方法类似,只不过接收的是一个 BiFunction<? super T,Throwable,? extends U> fn (两个入参)类型的参数,因此有 whenComplete 方法和 转换的功能 (thenApply)。

以Async结尾的方法可能会使用其它的线程去执行,如果使用相同的线程池,也可能会被同一个线程选中执行。

CompletableFuture<Integer> future = CompletableFuture
        .supplyAsync(() -> {
            ThreadUtil.sleep(100);
            return 20;
        })
        .handle((t, e)->{
            System.out.println("上一步的计算结果:" + t);
            System.out.println("上一步的异常信息:" + e);
            return 10; // whenComplete 区别,handle 有返回值
        });
System.out.println("A");
System.out.println(future.get());
System.out.println("B");
结果:
A
上一步的计算结果:20
上一步的异常信息:null
10
B
// 异常情况
CompletableFuture<Integer> future = CompletableFuture
        .supplyAsync(() -> {
            ThreadUtil.sleep(100);
            return 20/0;
        })
        .handle((t, e)->{
            System.out.println("上一步的计算结果:" + t);
            System.out.println("上一步的异常信息:" + e);
            return 10;
        });
System.out.println("A");
System.out.println(future.get());
System.out.println("B");
结果:
A
上一步的计算结果:null
上一步的异常信息:java.util.concurrent.CompletionException: java.lang.ArithmeticException: / by zero
10
B

结果处理转换 thenApply

当前阶段正常完成以后执行,而且当前阶段的执行的结果会作为下一阶段的输入参数。
thenApplyAsync默认是异步执行的。这里所谓的异步指的是不在当前线程内执行。
thenApply相当于回调函数(callback)。

CompletableFuture 由于有回调,可以不必等待一个计算完成而阻塞着调用线程,可以在一个结果计算完成之后紧接着执行某个Action。我们可以将这些操作串联起来。

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(() -> {
            ThreadUtil.sleep(100);
            return 20;
        })
        .thenApply((f1)->{
            System.out.println("F1:" + f1); // 上一步的结果
            return f1 * 2;
        })
        .thenApply((f2)->{
            System.out.println("F2:" + f2);
            return f2 * 2;
        })
        .thenApply((f3)->{
            System.out.println("F3:" + f3);
            return f3 * 2;
        });
System.out.println("A");
System.out.println("future:" + future.get());
System.out.println("B");

结果:
A
F1:20
F2:40
F3:80
future:160
B

这些方法不是马上执行的,也不会阻塞,而是前一个执行完成后继续执行下一个。
和 handle 方法的区别是,handle 会处理正常计算值和异常,不会抛出异常。而 thenApply 只会处理正常计算值,有异常则抛出。

纯消费 thenAccept、thenRun、thenAcceptBoth、runAfterBoth

thenAccept和thenRun都是无返回值的。如果说thenApply是不停的输入输出的进行生产,那么thenAccept和thenRun就是在进行消耗。它们是整个计算的最后两个阶段。

  • thenAccept 接收上一阶段的输出作为本阶段的输入
  • thenRun 根本不关心前一阶段的输出,根本不关心前一阶段的计算结果,因为它不需要输入参数

单纯的去消费结果而不会返回新的值,因些计算结果为 Void。

public CompletableFuture<Void> thenAccept(Consumer<? super T> action)
public CompletableFuture<Void> thenAcceptAsync(Consumer<? super T> action)
public CompletableFuture<Void> thenAcceptAsync(Consumer<? super T> action, Executor executor)
CompletableFuture<Void> future = CompletableFuture.supplyAsync(() -> {
    ThreadUtil.sleep(100);
    return 20;
}).thenAccept((c)->{
    System.out.println("CC:" + c);
}).thenAcceptAsync((c2)->{
    System.out.println("C2:" + c2);
});
System.out.println("A");
System.out.println(future.get());
System.out.println("B");

结果:
A
CC:20
C2:null
null
B
public <U> CompletableFuture<Void> thenAcceptBoth(CompletionStage<? extends U> other, BiConsumer<? super T,? super U> action)
public <U> CompletableFuture<Void> thenAcceptBothAsync(CompletionStage<? extends U> other, BiConsumer<? super T,? super U> action)
public <U> CompletableFuture<Void> thenAcceptBothAsync(CompletionStage<? extends U> other, BiConsumer<? super T,? super U> action, Executor executor)

thenAcceptBoththenAccept 相比,参数类型多了一个 CompletionStage<? extends U> otherBiConsumer第一个参数接收第一个supplyAsync 返回值,第二个参数接收CompletionStage返回值

CompletableFuture<Void> future = CompletableFuture.supplyAsync(() -> {
    System.out.println(Thread.currentThread().getName());
    ThreadUtil.sleep(100);
    return 20;
}).thenAcceptBoth(CompletableFuture.supplyAsync(() -> {
    System.out.println(Thread.currentThread().getName());
    return 2;
}), (a, b)->{
    System.out.println(Thread.currentThread().getName() + "-AA:" + a); // 20
    System.out.println(Thread.currentThread().getName() + "-BB:" + b); // 2
});
System.out.println("A");
System.out.println(future.get()); // 没有返回值 -》 null
System.out.println("B");

结果:
ForkJoinPool.commonPool-worker-1
ForkJoinPool.commonPool-worker-2
A
ForkJoinPool.commonPool-worker-1-AA:20
ForkJoinPool.commonPool-worker-1-BB:2
null
B
public CompletableFuture<Void> runAfterBoth(CompletionStage<?> other, Runnable action)
public CompletableFuture<Void> runAfterBothAsync(CompletionStage<?> other, Runnable action)
public CompletableFuture<Void> runAfterBothAsync(CompletionStage<?> other, Runnable action, Executor executor)

runAfterBoth 和以上方法不同,传一个 Runnable 类型的参数,不接收上一级的返回值

更彻底的:

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

以上是彻底的纯消费,完全忽略计算结果。

组合 thenCompose、thenCombine

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)

以上接收类型为 Function<? super T,? extends CompletionStage<U>> fn ,fn 接收上一级返回的结果,并返回一个新的 CompletableFuture。

CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> {
    System.out.println(Thread.currentThread().getName() + " A");
    ThreadUtil.sleep(100);
    return 20;
}).thenApply((f)->{ // 上一步结果
    System.out.println(Thread.currentThread().getName() + " B");
    return f + 10;
}).thenCompose((s)->{
    System.out.println(Thread.currentThread().getName() + " SS:" + s); // 上一步的结果
    return CompletableFuture.supplyAsync(() -> {
        System.out.println(Thread.currentThread().getName() + " C");
        return s * 5; // 返回一个新的 CompletableFuture。
    });
});
System.out.println("A");
System.out.println(future.get());
System.out.println("B");

结果:
ForkJoinPool.commonPool-worker-1 A
A
ForkJoinPool.commonPool-worker-1 B
ForkJoinPool.commonPool-worker-1 SS:30
ForkJoinPool.commonPool-worker-2 C
150
B
public <U,V> CompletableFuture<V> thenCombine(CompletionStage<? extends U> other, BiFunction<? super T,? super U,? extends V> fn)
public <U,V> CompletableFuture<V> thenCombineAsync(CompletionStage<? extends U> other, BiFunction<? super T,? super U,? extends V> fn)
public <U,V> CompletableFuture<V> thenCombineAsync(CompletionStage<? extends U> other, BiFunction<? super T,? super U,? extends V> fn, Executor executor)

两个CompletionStage是并行执行的,它们之间并没有先后依赖顺序,other并不会等待先前的CompletableFuture执行完毕后再执行。

CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> {
    ThreadUtil.sleep(1000);
    System.out.println(Thread.currentThread().getName());
    return 20;
}).thenApply((f)->{
    ThreadUtil.sleep(1000);
    System.out.println(Thread.currentThread().getName());
    return f + 10;
}).thenCombine(CompletableFuture.supplyAsync(() -> {
    System.out.println(Thread.currentThread().getName() + " thenCombineAsync");
    return 10;
}), (x, y)->{
    System.out.println(Thread.currentThread().getName() + " XX:" + x); // thenCombine 之前的结果
    System.out.println(Thread.currentThread().getName() + " YY:" + y); // 新CompletableFuture 结果
    return x + y;
});
System.out.println("A");
System.out.println(future.get());
System.out.println("B");

结果:
ForkJoinPool.commonPool-worker-2 thenCombineAsync
A
ForkJoinPool.commonPool-worker-1
ForkJoinPool.commonPool-worker-1
ForkJoinPool.commonPool-worker-1 XX:30
ForkJoinPool.commonPool-worker-1 YY:10
40
B

thenCombinesupplyAsync 不一定哪个先哪个后,是并行执行的。

任意一个方法执行完成就结束acceptEither、applyToEither

acceptEither方法是当任意一个 CompletionStage 完成的时候,action 这个消费者就会被执行。这个方法返回 CompletableFuture<Void>

public CompletableFuture<Void> acceptEither(CompletionStage<? extends T> other, Consumer<? super T> action)
public CompletableFuture<Void> acceptEitherAsync(CompletionStage<? extends T> other, Consumer<? super T> action)
public CompletableFuture<Void> acceptEitherAsync(CompletionStage<? extends T> other, Consumer<? super T> action, Executor executor)
Random random = new Random();
CompletableFuture<Void> future = CompletableFuture.supplyAsync(() -> {
    ThreadUtil.sleep(random.nextInt(1000));
    return "A";
}).acceptEither(CompletableFuture.supplyAsync(() -> {
    ThreadUtil.sleep(random.nextInt(1000));
    return "B";
}), (c) -> {
    System.out.println(c);
});
System.out.println(future.get()); // 两个CompletableFuture 哪个先执行完取哪个,无返回值

结果:
B
null
或者:
A
null

以上代码有时输出A,有时输出B,哪个Future先执行完就会根据它的结果计算。

applyToEither 方法是当任意一个 CompletionStage 完成的时候,fn会被执行,它的返回值会当作新的CompletableFuture<U>的计算结果。

public <U> CompletableFuture<U> applyToEither(CompletionStage<? extends T> other, Function<? super T,U> fn)
public <U> CompletableFuture<U> applyToEitherAsync(CompletionStage<? extends T> other, Function<? super T,U> fn)
public <U> CompletableFuture<U> applyToEitherAsync(CompletionStage<? extends T> other, Function<? super T,U> fn, Executor executor)
Random random = new Random();
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
    ThreadUtil.sleep(random.nextInt(1000));
    return "A";
}).applyToEither(CompletableFuture.supplyAsync(() -> {
    ThreadUtil.sleep(random.nextInt(1000));
    return "B";
}), (c) -> {
    System.out.println(c);
    return c + "DD";
});
System.out.println(future.get());

结果:
B
BDD
或者:
A
ADD

acceptEither 和 applyToEither 区别是: acceptEither 没有返回值,applyToEither 有返回值。

辅助方法allOf、anyOf

当所有的CompletableFuture都执行完后才往下执行,没有返回值。

public static CompletableFuture<Void> allOf(CompletableFuture<?>... cfs)

当任意一个CompletableFuture执行完后就会执行计算,计算的结果相同,返回一个Object类型的值。

public static CompletableFuture<Object> anyOf(CompletableFuture<?>... cfs)

使用CompletableFuture提升程序性能

如果每个操作都很简单的话,没有必要用这种多线程异步的方式,因为创建线程还需要时间,还不如直接同步执行来得快。

只有当每个操作很复杂需要花费相对很长的时间(比如,调用多个其它的系统的接口;比如,商品详情页面这种需要从多个系统中查数据显示的)的时候用CompletableFuture才合适,不然区别真的不大,还不如顺序同步执行。

定义一个对象:

@Data
public class UserInfo {
    private Integer id;
    private String name;
    private Integer jobId;
    private String jobDes;
    private Integer carId;
    private String carDes;
    private Integer homeId;
    private String homeDes;
}

这个对象里面的homeid,jobid,carid都是用于匹配对应的住房信息描述,职业信息描述,购车信息描述。

对于将id转换为描述信息的方式需要通过额外的sql查询,这里做了个简单的工具类来进行模拟:

import java.util.concurrent.TimeUnit;

public class QueryUtils {

    public String queryCar(Integer carId){
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return "car_desc";
    }
    public String queryJob(Integer jobId){
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return "job_desc";
    }
    public String queryHome(Integer homeId){
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return "home_desc";
    }
}

假设每次查询需要消耗1s,那么遍历的一个size为n的集合查询消耗的时间就是n * 3s。
下面来使用CompletableFuture提升性能:

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.function.Consumer;
import java.util.function.Supplier;
import java.util.stream.Collectors;

public class QueryUserService {

    private Supplier<QueryUtils> queryUtilsSupplier = new Supplier<QueryUtils>() {
        @Override
        public QueryUtils get() {
            return new QueryUtils();
        }
    };

    // 传统方式
    public UserInfo converUserInfo(UserInfo userInfo) {
        userInfo.setCarDes(queryUtilsSupplier.get().queryCar(userInfo.getCarId()));
        userInfo.setHomeDes(queryUtilsSupplier.get().queryHome(userInfo.getHomeId()));
        userInfo.setJobDes(queryUtilsSupplier.get().queryJob(userInfo.getJobId()));
        return userInfo;
    }

    // CompletableFuture方式
    public UserInfo converUserInfoForFuture(UserInfo userInfo) {
        CompletableFuture<String> getCarDesc = CompletableFuture.supplyAsync(() -> queryUtilsSupplier.get().queryCar(userInfo.getCarId()));
        getCarDesc.thenAccept((carDesc) -> userInfo.setCarDes(carDesc));

        CompletableFuture<String> getHomeDesc = CompletableFuture.supplyAsync(() -> queryUtilsSupplier.get().queryHome(userInfo.getHomeId()));
        getHomeDesc.thenAccept((homeDesc) -> userInfo.setHomeDes(homeDesc));

        CompletableFuture<String> getJobDesc = CompletableFuture.supplyAsync(() -> queryUtilsSupplier.get().queryJob(userInfo.getJobId()));
        getJobDesc.thenAccept((jobDesc) -> userInfo.setJobDes(jobDesc));

        CompletableFuture<Void> getUserInfo = CompletableFuture.allOf(getCarDesc, getHomeDesc, getJobDesc);
        getUserInfo.thenAccept(new Consumer<Void>() {
            @Override
            public void accept(Void result) {
                System.out.println("全部完成查询");
            }
        });
        getUserInfo.join();
        return userInfo;
    }
    public static void main(String[] args) {
        long begin = System.currentTimeMillis();
        // 多线程环境需要注意线程安全问题
        List<UserInfo> userInfoList = Collections.synchronizedList(new ArrayList<>());
        for (int i = 0; i <= 20; i++) {
            UserInfo userInfo = new UserInfo();
            userInfo.setId(i);
            userInfo.setName("username" + i);
            userInfo.setCarId(i);
            userInfo.setJobId(i);
            userInfo.setHomeId(i);
            userInfoList.add(userInfo);
        }
        QueryUserService queryUserService = new QueryUserService();
        //stream 查询一个用户花费3s  并行计算后一个用户1秒左右 查询21个用户花费21秒
        userInfoList.stream().map(userInfo -> {
            userInfo = queryUserService.converUserInfoForFuture(userInfo);
            return userInfo;
        }).collect(Collectors.toList());
        System.out.println("=============");
        long end = System.currentTimeMillis();
        System.out.println(end - begin);
    }
}

CompletableFuture使用详解

static方法说明

CompletableFuture的几个 static 方法,它们可以实例化一个 CompletableFuture 实例。

public static <U> CompletableFuture<U> completedFuture(U value)
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)
  • completedFuture 返回一个已经计算好的CompletableFuture。
  • runAsync 方法接收的是 Runnable 的实例,但是它没有返回值。
  • supplyAsync 方法是JDK8函数式接口,无参数,会返回一个结果。
  • 这两个方法是 executor 的升级,表示让任务在指定的线程池中执行,不指定的话,通常任务是在 ForkJoinPool.commonPool() 线程池中执行的。
supplyAsync()使用

静态方法runAsyncsupplyAsync允许我们相应地从RunnableSupplier功能类型中创建CompletableFuture实例。

CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
    System.out.println(Thread.currentThread().getName() + " A");
    return "Hello";
});
System.out.println(Thread.currentThread().getName() + " B");
System.out.println(future.get());

结果:
main B
ForkJoinPool.commonPool-worker-1 A
Hello
thenRun()使用

在两个任务任务A,任务B中,如果既不需要任务A的值也不想在任务B中引用,那么你可以将Runnable lambda 传递给thenRun()方法。
在下面的示例中,在调用future.get()方法之后,我们只需在控制台中打印一行:

模板

CompletableFuture.runAsync(() -> {}).thenRun(() -> {}); 
CompletableFuture.supplyAsync(() -> "resultA").thenRun(() -> {});
  • 第一行用的是 thenRun(Runnable runnable),任务 A 执行完执行 B,并且 B 不需要 A 的结果。
  • 第二行用的是 supplyAsync(Supplier supplier),任务 A 执行完执行 B,会返回resultA,但是 B 不需要 A 的结果。

实战

CompletableFuture<Void> completableFuture = CompletableFuture.supplyAsync(() -> {
    System.out.println(Thread.currentThread().getName() + " A");
    return "Hello";
}).thenRun(() -> {
    System.out.println(Thread.currentThread().getName() + " Computation finished.");
});
System.out.println(Thread.currentThread().getName() + " B");
System.out.println(completableFuture.get());

结果:
ForkJoinPool.commonPool-worker-1 A
main Computation finished.
main B
null
thenAccept()使用

在两个任务任务A,任务B中,如果你不需要在Future中有返回值,则可以用 thenAccept方法接收将计算结果传递给它。最后的future.get()调用返回Void类型的实例。

模板

CompletableFuture.runAsync(() -> {}).thenAccept(resultA -> {}); 
CompletableFuture.supplyAsync(() -> "resultA").thenAccept(resultA -> {});
  • 第一行中,runAsync不会有返回值,第二个方法thenAccept,接收到的resultA值为null,同时任务B也不会有返回结果。
  • 第二行中,supplyAsync有返回值,同时任务B不会有返回结果。

实战

CompletableFuture<String> completableFuture = CompletableFuture.supplyAsync(() -> {
    System.out.println(Thread.currentThread().getName() + " A");
    return "Hello";
});
CompletableFuture<Void> future = completableFuture.thenAccept(s -> {
    System.out.println(Thread.currentThread().getName() + " Computation returned: " + s);
});
System.out.println(Thread.currentThread().getName() + " B");
System.out.println(future.get());

结果:
ForkJoinPool.commonPool-worker-1 A
main Computation returned: Hello
main B
null
thenApply()使用

在两个任务任务A,任务B中,任务B想要任务A计算的结果,可以用thenApply方法来接受一个函数实例,用它来处理结果,并返回一个Future函数的返回值:
模板

CompletableFuture.runAsync(() -> {}).thenApply(resultA -> "resultB");
CompletableFuture.supplyAsync(() -> "resultA").thenApply(resultA -> resultA + " resultB");

第二行用的是 thenApply(Function fn),任务 A 执行完执行 B,B 需要 A 的结果,同时任务 B 有返回值。

实战

CompletableFuture<String> completableFuture = CompletableFuture.supplyAsync(() -> {
    ThreadUtil.sleep(1000L);
    System.out.println(Thread.currentThread().getName() + " A");
    return "Hello";
});
CompletableFuture<String> future = completableFuture.thenApply(s -> {
    System.out.println(Thread.currentThread().getName() + " B");
    return s + " World";
});
System.out.println(Thread.currentThread().getName() + " C");
System.out.println(future.get());

结果:
main C
ForkJoinPool.commonPool-worker-1 A
ForkJoinPool.commonPool-worker-1 B
Hello World

当然,多个任务的情况下,如果任务 B 后面还有任务 C,往下继续调用 .thenXxx() 即可。

thenCompose()使用

CompletableFuture API 的最佳场景是能够在一系列计算步骤中组合CompletableFuture实例。

这种组合结果本身就是CompletableFuture,允许进一步再续组合。这种方法在函数式语言中无处不在,通常被称为monadic设计模式。

简单说,Monad就是一种设计模式,表示将一个运算过程,通过函数拆解成互相连接的多个步骤。你只要提供下一步运算所需的函数,整个运算就会自动进行下去。

在下面的示例中,我们使用thenCompose方法按顺序组合两个Futures。

请注意,此方法采用返回CompletableFuture实例的函数。该函数的参数是先前计算步骤的结果。这允许我们在下一个CompletableFuture的lambda中使用这个值:

CompletableFuture<String> completableFuture = CompletableFuture
        .supplyAsync(() -> {
            ThreadUtil.sleep(1000L);
            System.out.println(Thread.currentThread().getName() + " A");
            return "Hello";
        })
        .thenCompose(s -> CompletableFuture.supplyAsync(() -> {
            System.out.println(Thread.currentThread().getName() + " B");
            return s + " World";
        }));
System.out.println(Thread.currentThread().getName() + " C");
System.out.println(completableFuture.get());

结果:
main C
ForkJoinPool.commonPool-worker-1 A
ForkJoinPool.commonPool-worker-1 B
Hello World

该thenCompose方法连同thenApply一样实现了结果的合并计算。但是他们的内部形式是不一样的,它们与Java 8中可用的Stream和Optional类的map和flatMap方法是有着类似的设计思路在里面的。

两个方法都接收一个CompletableFuture并将其应用于计算结果,但thenCompose(flatMap)方法接收一个函数,该函数返回相同类型的另一个CompletableFuture对象。此功能结构允许将这些类的实例继续进行组合计算。

thenCombine()、thenAcceptBoth()使用

取两个任务的结果。
如果要执行两个独立的任务,并对其结果执行某些操作,可以用Future的thenCombine方法。

模板

CompletableFuture<String> cfA = CompletableFuture.supplyAsync(() -> "resultA");
CompletableFuture<String> cfB = CompletableFuture.supplyAsync(() -> "resultB");
cfA.thenAcceptBoth(cfB, (resultA, resultB) -> {});
cfA.thenCombine(cfB, (resultA, resultB) -> "result A + B");

实战

CompletableFuture<String> completableFuture = CompletableFuture
        .supplyAsync(() -> {
            ThreadUtil.sleep(1000L);
            System.out.println(Thread.currentThread().getName() + " A");
            return "Hello";
        })
        .thenCombine(CompletableFuture.supplyAsync(() -> {
            System.out.println(Thread.currentThread().getName() + " B");
            return " World";
        }), (s1, s2) -> {
            System.out.println(Thread.currentThread().getName() + " C");
            return s1 + s2;
        });
System.out.println(Thread.currentThread().getName() + " D");
System.out.println(completableFuture.get());

结果:
ForkJoinPool.commonPool-worker-2 B
main D
ForkJoinPool.commonPool-worker-1 A
ForkJoinPool.commonPool-worker-1 C
Hello World

更简单的情况是,当你想要使用两个Future结果时,但不需要将任何结果值进行返回时,可以用thenAcceptBoth,它表示后续的处理不需要返回值,而 thenCombine 表示需要返回值:

CompletableFuture<Void> completableFuture = CompletableFuture
        .supplyAsync(() -> {
            ThreadUtil.sleep(1000L);
            System.out.println(Thread.currentThread().getName() + " A");
            return "Hello";
        })
        .thenAcceptBoth(CompletableFuture.supplyAsync(() -> {
            System.out.println(Thread.currentThread().getName() + " B");
            return " World";
        }), (s1, s2) -> {
            System.out.println(Thread.currentThread().getName() + " C," + s1 + s2);
        });
System.out.println(Thread.currentThread().getName() + " D");
System.out.println(completableFuture.get());

结果:
ForkJoinPool.commonPool-worker-2 B
main D
ForkJoinPool.commonPool-worker-1 A
ForkJoinPool.commonPool-worker-1 C,Hello World
null
thenApply()和thenCompose()之间的区别

在前面的部分中,我们展示了关于thenApply()thenCompose()的示例。这两个API都是使用的CompletableFuture调用,但这两个API的使用是不同的。

thenApply()

此方法用于处理先前调用的结果。但是,要记住的一个关键点是返回类型是转换泛型中的类型,是同一个CompletableFuture。

因此,当我们想要转换CompletableFuture 调用的结果时,效果是这样的 :

CompletableFuture<Integer> finalResult = compute().thenApply(s-> s + 1);
thenCompose()

该thenCompose()方法类似于thenApply()在都返回一个新的计算结果。但是,thenCompose()使用前一个Future作为参数。它会直接使结果变新的Future,而不是我们在thenApply()中到的嵌套Future,而是用来连接两个CompletableFuture,是生成一个新的CompletableFuture:

CompletableFuture<Integer> computeAnother(Integer i){
    return CompletableFuture.supplyAsync(() -> 10 + i);
}
CompletableFuture<Integer> finalResult = compute().thenCompose(this::computeAnother);

因此,如果想要继续嵌套链接CompletableFuture 方法,那么最好使用thenCompose()。

并行运行多个任务

当我们需要并行执行多个任务时,我们通常希望等待所有它们执行,然后处理它们的组合结果。

CompletableFuture.allOf静态方法允许等待所有的完成任务:
API

public static CompletableFuture<Void> allOf(CompletableFuture<?>... cfs){...}

实战

CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> "Hello");
CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> "Beautiful");
CompletableFuture<String> future3 = CompletableFuture.supplyAsync(() -> "World");
CompletableFuture<Void> combinedFuture = CompletableFuture.allOf(future1, future2, future3);
// ...
combinedFuture.get();
assertTrue(future1.isDone());
assertTrue(future2.isDone());
assertTrue(future3.isDone());

请注意,CompletableFuture.allOf()的返回类型是CompletableFuture <Void>。这种方法的局限性在于它不会返回所有任务的综合结果。相反,你必须手动从Futures获取结果。幸运的是,CompletableFuture.join()方法和Java 8 Streams API可以解决:

String combined = Stream.of(future1, future2, future3)
  .map(CompletableFuture::join)
  .collect(Collectors.joining(" "));
assertEquals("Hello Beautiful World", combined);

CompletableFuture 提供了 join() 方法,它的功能和 get() 方法是一样的,都是阻塞获取值,它们的区别在于 join() 抛出的是 unchecked Exception。这使得它可以在Stream.map()方法中用作方法引用。

异常处理

说到这里,我们顺便来说下 CompletableFuture 的异常处理。这里我们要介绍两个方法:

public CompletableFuture<T> exceptionally(Function<Throwable, ? extends T> fn);
public <U> CompletionStage<U> handle(BiFunction<? super T, Throwable, ? extends U> fn);

看下代码

CompletableFuture.supplyAsync(() -> "resultA")
    .thenApply(resultA -> resultA + " resultB")
    .thenApply(resultB -> resultB + " resultC")
    .thenApply(resultC -> resultC + " resultD");

上面的代码中,任务 A、B、C、D 依次执行,如果任务 A 抛出异常(当然上面的代码不会抛出异常),那么后面的任务都得不到执行。如果任务 C 抛出异常,那么任务 D 得不到执行。

那么我们怎么处理异常呢?看下面的代码,我们在任务 A 中抛出异常,并对其进行处理:

CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
    throw new RuntimeException();
}).exceptionally(ex -> "errorResultA")
        .thenApply(resultA -> resultA + " resultB")
        .thenApply(resultB -> resultB + " resultC")
        .thenApply(resultC -> resultC + " resultD");

System.out.println(future.join());

上面的代码中,任务 A 抛出异常,然后通过 .exceptionally() 方法处理了异常,并返回新的结果,这个新的结果将传递给任务 B。所以最终的输出结果是:errorResultA resultB resultC resultD

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值