详解 java CompletableFuture

背景知识

要理解 CompletableFuture,首先要弄懂什么是 Future。由于后者是前者的扩展。本文并不打算详细的介绍 Future,毕竟不是本文的重点。html

Future是java1.5增长的一个接口,提供了一种异步并行计算的能力。好比说主线程须要执行一个复杂耗时的计算任务,咱们能够经过future把这个任务放在独立的线程(池)中执行,而后主线程继续处理其余任务,处理完成后再经过Future获取计算结果。java

这里经过一个简单的示例带你理解下 Future。数据库

咱们有两个服务,一个是用户服务能够获取用户信息,一个是地址服务,能够经过用户id获取地址信息。以下,编程

@AllArgsConstructor
@Data
public class PersonInfo {
    private long id;
    private String name;
    private int age;
}
public class PersonService {

    public PersonInfo getPersonInfo(Long personId) throws InterruptedException {
        Thread.sleep(500);//模拟调用耗时
        //真实项目中,这里大部分时候是经过dao从数据库获取
        return new PersonInfo(personId, "zhangsan", 30); //返回一条
    }
}
@AllArgsConstructor
public class AddressInfo {
    private String addressLine;
    private String city;
    private String province;
    private String country;
}
public class AddressService {

    public AddressInfo getAddress(long personId) throws InterruptedException {
        Thread.sleep(600); //模拟调用耗时
        System.out.println("id:" + personId);
        //真实项目中,这里大部分时候是经过dao从数据库获取
        return new AddressInfo("胜利大街143号", "北京市", "北京", "中国");
    }
}

而后咱们演示下如何在主线程使用 Future 来进行异步调用。segmentfault

public class FutureTest {

    public static void main(String[] args) throws InterruptedException, ExecutionException {

        long startTime = System.currentTimeMillis();

        PersonService personService = new PersonService();
        AddressService addressService = new AddressService();
        long personId = 100L;

        //调用用户服务获取用户基本信息
        FutureTask<PersonInfo> personInfoFutureTask = new FutureTask<>(new Callable<PersonInfo>() {
            @Override
            public PersonInfo call() throws Exception {
                return personService.getPersonInfo(personId);
            }
        });
        new Thread(personInfoFutureTask).start();

        Thread.sleep(300); //模拟主线程其它操做耗时

        FutureTask<AddressInfo> addressInfoFutureTask = new FutureTask<>(new Callable<AddressInfo>() {
            @Override
            public AddressInfo call() throws Exception {
                return addressService.getAddress(personId);
            }
        });
        new Thread(addressInfoFutureTask).start();

        PersonInfo personInfo = personInfoFutureTask.get();//获取我的信息结果
        AddressInfo addressInfo = addressInfoFutureTask.get();//获取地址信息结果

        System.out.println("总共用时" + (System.currentTimeMillis() - startTime) + "ms");

    }
}

输出:设计模式

总共用时909ms

很明显,若是咱们不使用 Future,而是在主线程串行的调用,耗时会是 500 + 300 + 600 = 1400 毫秒。经过 Future提供的异步计算功能,咱们能够多个任务并行的执行,从而提升执行效率。框架

我但愿你能仔细的看上面的这个示例,由于后面讲到 CompletableFuture 我会使用同一个示例。异步

基本介绍

经过上面的例子来看,彷佛 Future 自己已经很强大了。那么 CompletableFuture 又是作啥的呢?ide

虽然Future以及相关使用方法提供了异步执行任务的能力,可是对于结果的获取倒是很不方便,只能经过阻塞或者轮询的方式获得任务的结果。在上面的示例中,personInfoFutureTask.get() 就是阻塞调用,在线程获取结果以前get方法会一直阻塞。异步编程

轮询的方式在上面的示例中没有,其实也很简单。是Future提供了一个isDone方法,咱们能够在程序中不断的轮询这个方法查询执行结果。

可是,不管是阻塞方式仍是轮询方式,都不够好。

  • 阻塞的方式和异步编程的初衷相违背
  • 轮询的方式会耗费无谓的CPU资源

正是在这样的背景下,CompletableFuture 在java8横空出世。CompletableFuture提供了一种机制可让任务执行完成后通知监听的一方,相似设计模式中的观察者模式。

使用示例

首先咱们来看看,上面Future那个示例,若是是用 CompletableFuture 该怎么作?

我先给出代码,

public class CompetableFutureTest {

    public static void main(String[] args) throws InterruptedException, ExecutionException {

        long startTime = System.currentTimeMillis();

        PersonService personService = new PersonService();
        AddressService addressService = new AddressService();
        long personId = 100L;

        CompletableFuture<PersonInfo> personInfoCompletableFuture = CompletableFuture.supplyAsync(() -> personService.getPersonInfo(personId));
        CompletableFuture<AddressInfo> addressInfoCompletableFuture = CompletableFuture.supplyAsync(() -> addressService.getAddress(personId));

        Thread.sleep(300); //模拟主线程其它操做耗时

        PersonInfo personInfo = personInfoCompletableFuture.get();//获取我的信息结果
        AddressInfo addressInfo = addressInfoCompletableFuture.get();//获取地址信息结果

        System.out.println("总共用时" + (System.currentTimeMillis() - startTime) + "ms");

    }
}

首先咱们实现一样的功能代码简洁不少。supplyAsync 支持异步地执行咱们指定的方法,这个例子中的异步执行方法是调用service。咱们也可使用 Executor 执行异步程序,默认是 ForkJoinPool.commonPool()。

另外经过这个示例,能够发现咱们彻底可使用 CompletableFuture 代替 Future。

固然 CompletableFuture 的功能远不止与此,否则它的存在就没有意义了。CompletableFuture 提供了几十种方法辅助咱们操做异步任务,用好了这些方法能够写出更加简洁,高效的代码。好比下面这个例子:

CompletableFuture<PersonInfo> personInfoCompletableFuture = CompletableFuture.supplyAsync(() -> personService.getPersonInfo(personId));
        CompletableFuture<AddressInfo> addressInfoCompletableFuture = CompletableFuture.supplyAsync(() -> addressService.getAddress(personId));


        final CompletableFuture<Void> completableFutureAllOf =
                CompletableFuture.allOf(personInfoCompletableFuture, addressInfoCompletableFuture);

        completableFutureAllOf.get(); //执行时间以最长那个任务为准

        PersonInfo personInfo = personInfoCompletableFuture.get();//立刻返回
        AddressInfo addressInfo = addressInfoCompletableFuture.get();//立刻返回

这个示例中,allOf可让咱们把多个异步任务结果的获取整合起来,这样操做更简单,代码更简洁。

前面提到了它能够解决的痛点,就是提供了一种相似观察者模式的机制,当异步的计算结果完成后能够通知监听者。下面来看个示例,

CompletableFuture<PersonInfo> personInfoCompletableFuture = CompletableFuture.supplyAsync(() -> personService.getPersonInfo(personId));
        CompletableFuture<AddressInfo> addressInfoCompletableFuture = CompletableFuture.supplyAsync(() -> addressService.getAddress(personId));


        final CompletableFuture<Void> completableFutureAllOf =
                CompletableFuture.allOf(personInfoCompletableFuture, addressInfoCompletableFuture);

        //监听执行结果,整合两个任务的结果进一步处理
        final CompletableFuture<PersonAndAddress> personAndAddressCompletableFuture = completableFutureAllOf.thenApply((voidInput) ->
                new PersonAndAddress(personInfoCompletableFuture.join(), addressInfoCompletableFuture.join()));

        personAndAddressCompletableFuture.join();//以时间长的任务为准

在上面这个示例中,当两个异步任务执行完毕后,咱们能够经过thenApply监听到结果并进行处理。

CompletableFuture 还有不少好玩有用的功能,若是感兴趣能够自行研究下。

总结

经过前面的讲解,你应该对 Future 以及它的扩展接口 CompletableFuture 都有了比较深刻的认识。

我我的的建议是若是你的项目是基于java8,大部分状况你应该用后者而不是前者。若是你的项目是java8以前的版本,也建议你使用第三方的工具好比 Guava 等框架提供的Future工具类。

参考:


关注公众号:犀牛饲养员的技术笔记

我的博客:http://www.machengyu.net

csdn博客: https://blog.csdn.net/pony_ma...

思否: https://segmentfault.com/u/ma...

相关文章
相关标签/搜索