要理解 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方法,咱们能够在程序中不断的轮询这个方法查询执行结果。
可是,不管是阻塞方式仍是轮询方式,都不够好。
正是在这样的背景下,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工具类。
参考:
关注公众号:犀牛饲养员的技术笔记
csdn博客: https://blog.csdn.net/pony_ma...