线程池整合CompletableFuture实现异步编程
Gitee地址:https://gitee.com/yuyuuyuy/micro-mall
文章目录
前言
线程池是处理一个异步任务或并发任务的重要手段,然而,如果多个线程存在依赖组合的话,那么我们就需要使用
CompletableFuture来对线程进行异步编排。使用到的技术有:线程池技术,CompletableFuture
一、异步编程方案探讨
场景说明:电商项目中,一个任务的执行会包含很多个子任务,比如查询商品详情页,我们需要获取sku的基本信息(0.5s),获取sku的图片信息(0.5s),获取sku的促销信息(1s),获取spu详情(1s),如果这一系列任务都是顺序执行的话,需要0.5+0.5+1+1=3s,但这样效率就太低了,这些任务其实都是可以异步执行的,如果用多个线程让这些任务异步执行,那么只需要1s,效率就大大提升了。然而,有些场景下,不是所有线程都适合异步执行的,比如线程B需要线程A执行完后才能执行,但是A线程和C线程可以异步执行,那么我们就需要使用CompletableFuture来对线程实现异步编排。
二、线程池和CompletableFuture介绍及效果展示
1.线程池
核心是设置七大参数
1.corePoolSize:线程池基本大小
2.maximumPoolSize:线程池最大大小
3.keepAliveTime:线程存活保持时间
4.timeUnit: 时间单位
5.workQueue:任务队列
6.threadFactory:线程工厂
7.handler:线程饱和策略
例子:一个线程池,corePoolSize=7,maximumPoolSize=20,workQueue=50,100个并发进来,
那么7个会立即执行,然后再开20-7=13个线程执行,50个线程进入队列等待执行,剩下100-20-50=30个就拒绝(如果拒绝策略是直接抛弃的话)
``
2.CompletableFuture异步编排
CompletableFuture使用步骤
1.CompletableFuture是基于线程池的,可以先自定义线程池,然后注入到CompletableFuture中
2.创建CompletableFuture对象,执行CompletableFuture的方法。
并行执行方法:
supplyAsync方法,
runAsync方法:
串行执行方法:
thenRun方法
thenRunAsync方法
thenAccept方法
thenAcceptAsync方法
thenApply方法
thenApplyAsync方法
解读:then就是串行化执行,没有then就是异步执行,Run方法没有参数没有返回值,Accept有入参无返回值,Apply有入参有返回值,有Async就表明会使用新线程来串行化方法,没有Async就表明会使用原来的线程执行串行化方法
3.对这些CompletableFuture对象进行多任务组合
anyOf(CompletableFuture<?>... cfs):只要有一个CompletableFuture对象执行完就往下执行 allOf(CompletableFuture<?>… cfs):全部的CompletableFuture对象执行完才继续往下执行
3.效果展示
1.串行化
场景说明:模拟商品信息串行化获取,且在自定义的线程池中执行,先查询商品id,再根据该商品id查询库存。
可见,商品id和库存是串行化执行的,但是这两线程和主线程是并行化执行的,所以main方法先结束后查库存方法才结束。
2.并行化
查询商品图片信息,查询商品属性,查询商品介绍并行化执行,其中查询商品属性等待3s才执行,
可见,虽然查询商品属性在代码里是第二个执行的,但是因为等待了3秒,所以查询商品介绍就先执行了,体现了线程的并行化
3.多任务组合
查询商品图片信息,查询商品属性,查询商品介绍并行化执行,其中查询商品属性等待3s才执行,使用anyOf方法,任何一个线程执行完都返回结果然后往下执行。
从结果可以看出,查询图片信息线程先执行完毕然后返回结果,其他线程接着执行
三、代码实现:
1.自定义线程池:
@Bean
public ThreadPoolExecutor threadPoolExecutor() {
return new ThreadPoolExecutor(20, 200, 10, TimeUnit.SECONDS, new LinkedBlockingDeque<>(1000), Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy());
}
//线程池,10个线程
public static ExecutorService executorService = Executors.newFixedThreadPool(10);
2.串行化:
/**
* 模拟商品信息串行化获取,且在自定义的线程池中执行
* 先查询商品id,再根据该商品id查询库存
* thenRunAsync获取不到上一步的结果
* thenAcceptAsync可获取上一步的结果
*/
@Test
public void getProBySerial() {
ExecutorService executorService = Executors.newFixedThreadPool(10);
System.out.println("===========main开始===========");
final CompletableFuture<Void> Future = CompletableFuture.supplyAsync(() -> {
System.out.println("当前线程:" + Thread.currentThread().getId());
int i = 1283;
System.out.println("查商品id:" + i);
return i;
}, executorService).thenAcceptAsync((i) -> {
int j = i - 1000;
System.out.println("根据商品id" + i + "查库存,库存为:" + j);
}, executorService);
System.out.println("===========main结束===========");
}
3.异步编排或并行化
/**
* 模拟异步获取商品信息,且在自定义的线程池中执行
* allOf.get()可等全部执行完后,再一起获取全部信息
* anyOf.get()在任何流程执行完后,都可返回信息
*/
public void getProByParallel() throws ExecutionException, InterruptedException {
ExecutorService executorService = Executors.newFixedThreadPool(10);
System.out.println("===========main开始===========");
//模拟商品信息异步获取流程
CompletableFuture<String> futureImg = CompletableFuture.supplyAsync(() -> {
System.out.println("查询商品图片信息");
return "查询商品图片信息";
}, executorService);
CompletableFuture<Void> futureAttr = CompletableFuture.supplyAsync(() -> {
try {
Thread.sleep(3000);
System.out.println("查询商品属性");
} catch (InterruptedException e) {
e.printStackTrace();
}
return null;
}, executorService);
CompletableFuture<Void> futureDesc = CompletableFuture.supplyAsync(() -> {
System.out.println("查询商品介绍");
return null;
}, executorService);
CompletableFuture<Object> anyOf = CompletableFuture.anyOf(futureImg, futureAttr, futureDesc);
// CompletableFuture<Void> allOf=CompletableFuture.allOf(futureImg,futureAttr,futureDesc);
System.out.println("最先执行完的是:" + anyOf.get());
// allOf.get();
System.out.println("图片:" + futureImg.get() + ",属性:" + futureAttr.get() + ",描述:" + futureDesc.get());
System.out.println("===========main结束===========");
}
总结
使用线程池结合 CompletableFuture异步编排,我们可以大大提高线程的并发性,优化程序的执行效率。