一、异步复习
创造线程的四种方式
- 继承Thread类
public static class Thread01 extends Thread {
@Override
public void run() {
System.out.println("当前线程:" + Thread.currentThread().getId());
int i = 10 / 2;
System.out.println("运行结果:" + i);
}
}
public static void main(String[] args){
Thread thread=new Thread01();
thread.start();
}
- 实现Runnable接口
public static class Runable01 implements Runnable {
@Override
public void run() {
System.out.println("当前线程:" + Thread.currentThread().getId());
int i = 10 / 2;
System.out.println("运行结果:" + i);
}
}
public static void main(String[] args){
Runable01 r=new Runable01();
new Thread(r).start;
}
- 实现Callable接口
public static class Callable01 implements Callable<Integer> {
@Override
public Integer call() throws Exception {
System.out.println("当前线程:" + Thread.currentThread().getId());
int i = 10 / 2;
System.out.println("运行结果:" + i);
return i;
}
}
public static void main(String[] args){
FutureTask <Integer> fu= new FutureTask <>(new Callable01();
new Thread(fu).start;
}
- 线程池
public static ExecutorService executor = Executors.newFixedThreadPool(10);
1.1线程池详解(ExecutorService)
为什么要使用线程池:通过1,2,3方式获取线程无法控制资源。当有多个异步任务时,1,2,3方式就会创造多个线程,这会占用极大的内存空间。当前主机如果只有1g资源只能同时执行200个线程,那么就创造200个线程的线程池,每次只有200个线程同时执行,达到了控制资源的效果。
线程池的创建方式
(1)使用Executors
public static ExecutorService executor = Executors.newFixedThreadPool(10);
(2)new ThreadPoolExecutor();
private static void threadPool() {
ExecutorService threadPool = new ThreadPoolExecutor(
200,
10,
10L,
TimeUnit.SECONDS,
new LinkedBlockingDeque<Runnable>(10000),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy()
);
//定时任务的线程池
ExecutorService service = Executors.newScheduledThreadPool(2);
}
七大参数
- corePoolSIze:核心线程数,线程池创建之后就准备
- maximumPoolSize:最大线程数量,在核心线程池满了之后可以容纳的最大的线程数量
- keepAliveTime:当线程数量大于核心线程数,该值控制线程在多久后终止释放
- unit:keepAliveTime时间单位
- workQueue:阻塞队列,用来存储等待执行的任务,如果执行的线程数大于核心线程数,就会被送入阻塞队列阻塞。
- threadFactory:创建线程的工厂
- handler:拒绝策略,如果线程满了线程池采用的拒绝策略
ThreadPoolExecutor的运行流程
- (1)线程池创建好,准备好核心线程数,等待任务执行
- (2)任务开始执行,首先核心线程数准备好的线程进行执行
- 如果核心线程数满了,就会将未执行的线程送入阻塞队列进行阻塞
- 如果阻塞队列满了,就会根据最大线程数量开辟线程
- 如果开辟的最大线程数量仍少于请求的线程数,剩下的线程将会被拒绝。
线程池使用的好处
1、降低资源的消耗:通过重复利用已经创建好的线程降低线程的创建和销毁带来的损耗
2、提高相应速度:因为线程池中的线程数没有超过线程池的最大上限时,有的线程处于等待分配任务
的状态,当任务来时无需创建新的线程就能执行
3、提高线程的相应速度:线程池会根据当前系统特点对池内的线程进行优化处理,减少创建和销毁线程带来
的系统开销。无限的创建和销毁线程不仅消耗系统资源,还降低系统的稳定性,使
用线程池进行统一分配
二、CompletableFuture 异步编排
业务场景:
查询商品详情页的逻辑比较复杂,有些数据还需要远程调用,必然需要花费更多的时间。
假如商品详情页的每个查询,需要如下标注的时间才能完成
那么,用户需要5.5s 后才能看到商品详情页的内容。很显然是不能接受的。
如果有多个线程同时完成这6 步操作,也许只需要1.5s 即可完成响应。
如果开辟6个线程同时执行时不行的,2,3,4,5号业务就需要在1号业务执行完成之后才能执行(获取sku信息之后才能获取到spu信息)。所以CompletableFuture可以用来进行异步编排
三、CompletableFuture 异步编排在项目中的使用
创建线程池
@EnableConfigurationProperties(ThreadPoolConfigProperties.class)//开启ThreadPoolConfigProperties的自动配置
@Configuration
public class MyThreadConfig {
@Bean
public ThreadPoolExecutor threadPoolExecutor(ThreadPoolConfigProperties pool) {
return new ThreadPoolExecutor(
//将线程池设置为可配置的、从配置文件中获取
pool.getCoreSize(),
pool.getMaxSize(),
pool.getKeepAliveTime(),
TimeUnit.SECONDS,
new LinkedBlockingDeque<>(100000),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy()
);
}
}
异步任务
sku基本属性获取完,才能获取到spu相关的属性,所以关于spu相关属性的获取需要在sku基本属性获取完之后执行
sku基本信息的获取---->spu的销售属性组合获取---->获取spu的介绍---->获取spu的规格参数信息
sku的图片信息单独执行
@Override
public SkuItemVo item(Long skuId) throws ExecutionException, InterruptedException {
SkuItemVo skuItemVo = new SkuItemVo();
CompletableFuture<SkuInfoEntity> infoFuture = CompletableFuture.supplyAsync(() -> {
//1、sku基本信息的获取 pms_sku_info
SkuInfoEntity info = this.getById(skuId);
skuItemVo.setInfo(info);
return info;
}, executor);
CompletableFuture<Void> saleAttrFuture = infoFuture.thenAcceptAsync((res) -> {
//3、获取spu的销售属性组合
List<SkuItemSaleAttrVo> saleAttrVos = skuSaleAttrValueService.getSaleAttrBySpuId(res.getSpuId());
skuItemVo.setSaleAttr(saleAttrVos);
}, executor);
CompletableFuture<Void> descFuture = infoFuture.thenAcceptAsync((res) -> {
//4、获取spu的介绍 pms_spu_info_desc
SpuInfoDescEntity spuInfoDescEntity = spuInfoDescService.getById(res.getSpuId());
skuItemVo.setDesc(spuInfoDescEntity);
}, executor);
CompletableFuture<Void> baseAttrFuture = infoFuture.thenAcceptAsync((res) -> {
//5、获取spu的规格参数信息
List<SpuItemAttrGroupVo> attrGroupVos = attrGroupService.getAttrGroupWithAttrsBySpuId(res.getSpuId(), res.getCatalogId());
skuItemVo.setGroupAttrs(attrGroupVos);
}, executor);
// Long spuId = info.getSpuId();
// Long catalogId = info.getCatalogId();
//2、sku的图片信息 pms_sku_images
CompletableFuture<Void> imageFuture = CompletableFuture.runAsync(() -> {
List<SkuImagesEntity> imagesEntities = skuImagesService.getImagesBySkuId(skuId);
skuItemVo.setImages(imagesEntities);
}, executor);
//等到所有任务都完成
CompletableFuture.allOf(saleAttrFuture,descFuture,baseAttrFuture,imageFuture,seckillFuture).get();
return skuItemVo;
}