CompletableFuture实现异步编排

为什么需要异步执行?

场景:电商系统中获取一个完整的商品信息可能分为以下几步:①获取商品基本信息 ②获取商品图片信息 ③获取商品促销活动信息 ④获取商品各种类的基本信息 等操作,如果使用串行方式去执行这些操作,假设每个操作执行1s,那么用户看到完整的商品详情就需要4s的时间,如果使用并行方式执行这些操作,可能只需要1s就可以完成。所以这就是异步执行的好处。

JDK5的Future接口

Future接口用于代表异步计算的结果,通过Future接口提供的方法可以查看异步计算是否执行完成,或者等待执行结果并获取执行结果,同时还可以取消执行。

列举Future接口的方法:

  • get():获取任务执行结果,如果任务还没完成则会阻塞等待直到任务执行完成。如果任务被取消则会抛出CancellationException异常,如果任务执行过程发生异常则会抛出ExecutionException异常,如果阻塞等待过程中被中断则会抛出InterruptedException异常。

  • get(long timeout,Timeunit unit):带超时时间的get()方法,如果阻塞等待过程中超时则会抛出TimeoutException异常。

  • cancel():用于取消异步任务的执行。如果异步任务已经完成或者已经被取消,或者由于某些原因不能取消,则会返回false。如果任务还没有被执行,则会返回true并且异步任务不会被执行。如果任务已经开始执行了但是还没有执行完成,若mayInterruptIfRunning为true,则会立即中断执行任务的线程并返回true,若mayInterruptIfRunning为false,则会返回true且不会中断任务执行线程。

  • isCanceled():判断任务是否被取消,如果任务在结束(正常执行结束或者执行异常结束)前被取消则返回true,否则返回false。

  • isDone():判断任务是否已经完成,如果完成则返回true,否则返回false。需要注意的是:任务执行过程中发生异常、任务被取消也属于任务已完成,也会返回true。

使用Future接口和Callable接口实现异步执行:

publicstaticvoidmain(String[] args) {
	// 快速创建线程池
	ExecutorServiceexecutorService= Executors.newFixedThreadPool(4);
	// 获取商品基本信息(可以使用Lambda表达式简化Callable接口,这里为了便于观察不使用)
	Future<String> future1 = executorService.submit(newCallable<String>() {
		@Override
		public String call()throws Exception {
			return"获取到商品基本信息";
		}
	});
	// 获取商品图片信息
	Future<String> future2 = executorService.submit(newCallable<String>() {
		@Override
		public String call()throws Exception {
			return"获取商品图片信息";
		}
	});
	// 获取商品促销信息
	Future<String> future3 = executorService.submit(newCallable<String>() {
		@Override
		public String call()throws Exception {
			return"获取商品促销信息";
		}
	});
	// 获取商品各种类基本信息
	Future<String> future4 = executorService.submit(newCallable<String>() {
		@Override
		public String call()throws Exception {
			return"获取商品各种类基本信息";
		}
	});
        // 获取结果
	try {
		System.out.println(future1.get());
		System.out.println(future2.get());
		System.out.println(future3.get());
		System.out.println(future4.get());
	} catch (InterruptedException | ExecutionException e) {
		e.printStackTrace();
	}finally {
		executorService.shutdown();
	}
}
复制代码
既然Future可以实现异步执行并获取结果, 为什么还会需要CompletableFuture?

简述一下Future接口的弊端:

  • 不支持手动完成

  • 当提交了一个任务,但是执行太慢了,通过其他路径已经获取到了任务结果,现在没法把这个任务结果通知到正在执行的线程,所以必须主动取消或者一直等待它执行完成。

  • 不支持进一步的非阻塞调用

  • 通过Future的get()方法会一直阻塞到任务完成,但是想在获取任务之后执行额外的任务,因为 Future 不支持回调函数,所以无法实现这个功能。

  • 不支持链式调用

  • 对于Future的执行结果,想继续传到下一个Future处理使用,从而形成一个链式的pipline调用,这在 Future中无法实现。

  • 不支持多个 Future 合并

  • 比如有10个Future并行执行,想在所有的Future运行完毕之后,执行某些函数,是无法通过Future实现的。

  • 不支持异常处理

  • Future的API没有任何的异常处理的api,所以在异步运行时,如果出了异常问题不好定位。

使用Future接口可以通过get()阻塞式获取结果或者通过轮询+isDone()非阻塞式获取结果,但是前一种方法会阻塞,后一种会耗费CPU资源,所以JDK的Future接口实现异步执行对获取结果不太友好,所以在JDK8时推出了CompletableFuture实现异步编排


CompletableFuture的使用

CompletableFuture概述

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

publicclassCompletableFuture<T> implementsFuture<T>, CompletionStage<T>
复制代码

CompletableFuture类实现了Future接口和CompletionStage接口,即除了可以使用Future接口的所有方法之外,CompletionStage<T>接口提供了更多方法来更好的实现异步编排,并且大量的使用了JDK8引入的函数式编程概念。后面会细致的介绍常用的API。


① 创建CompletableFuture的方式

使用new关键字创建
// 无返回结果
CompletableFuture<String> completableFuture = newCompletableFuture<>();
// 已知返回结果
CompletableFuture<String> completableFuture = newCompletableFuture<>("result");
// 已知返回结果(底层其实也是带参数的构造器赋值)
CompletableFuture<String> completedFuture = CompletableFuture.completedFuture("result");
复制代码

创建一个返回结果类型为String的CompletableFuture,可以使用Future接口的get()方法获取该值(同样也会阻塞)。

可以使用无参构造器返回一个没有结果的CompletableFuture,也可以通过构造器的传参CompletableFuture设置好返回结果,或者使用CompletableFuture.completedFuture(U value)构造一个已知结果的CompletableFuture。

使用CompletableFuture类的静态工厂方法(常用)
  1. runAsync() 无返回值

// 使用默认线程池publicstatic CompletableFuture<Void> runAsync(Runnable runnable)// 使用自定义线程池(推荐)publicstatic CompletableFuture<Void> runAsync(Runnable runnable,Executor executor)复制代码

runAsync()方法的参数是Runnable接口,这是一个函数式接口,不允许返回值。当需要异步操作且不关心返回结果的时候可以使用runAsync()方法。

// 例子publicstaticvoidmain(String[] args) {
    // 快速创建线程池ExecutorServiceexecutor= Executors.newFixedThreadPool(4);
    try {
        // 通过Lambda表达式实现Runnable接口
        CompletableFuture.runAsync(()-> System.out.println("获取商品基本信息成功"), executor).get();
    } catch (InterruptedException | ExecutionException e) {
        e.printStackTrace();
    }finally {
        executor.shutdown();
    }
}
复制代码
  1. supplyAsync() 有返回值

// 使用默认线程池publicstatic <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier)// 使用自定义线程池(推荐)publicstatic <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier,Executor executor)复制代码

supplyAsync()方法的参数是Supplier<U>供给型接口(无参有返回值),这也是一个函数式接口,U是返回结果值的类型当需要异步操作且关心返回结果的时候,可以使用supplyAsync()方法。

// 例子publicstaticvoidmain(String[] args) {
	// 快速创建线程池
	ExecutorServiceexecutor= Executors.newFixedThreadPool(4);
	try {
		// 通过Lambda表达式实现执行内容,并返回结果通过CompletableFuture接收
		CompletableFuture<String> completableFuture = CompletableFuture.supplyAsync(() -> {
			System.out.println("获取商品信息成功");
			return"信息";
		}, executor);
		// 输出结果
		System.out.println(completableFuture.get());
	} catch (InterruptedException | ExecutionException e) {
		e.printStackTrace();
	}finally {
		executor.shutdown();
	}
}  
复制代码
关于第二个参数Executor executor说明

在没有指定第二个参数(即没有指定线程池)时,CompletableFuture直接使用默认的ForkJoinPool.commonPool()作为它的线程池执行异步代码。

在实际生产中会使用自定义的线程池来执行异步代码,具体可以参考另一篇文章深入理解线程池ThreadPoolExecutor - 掘金 (juejin.cn),里面的第二节有生产中怎么创建自定义线程的例子,可以参考一下。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值