概述

异步是说,A发起一个操作后(一般都是比较耗时的操作,如果不耗时的操作就没有必要异步了),可以继续自顾自的处理它自己的事儿,不用干等着这个耗时操作返回。
异步是目的,而多线程是实现这个目的的方法。
在java中,异步都是通过Thread类开启一个线程的方式实现的。

初始化线程的四种方式

我们以后再业务代码里面,前三种启动线程的方式都不用。【将所有的多线程异步任务都交给线程池执行】
->且每个系统拥有一个或几个线程池,每个任务都从系统线程池中初始化线程;而不是每需要一个线程就创建一次线程池。
->当前系统中池只有一两个,每个异步任务,提交给线程池让他自己去执行就行。

1、继承Thread类;

  1. package com.atguigu.gulimall.search.thread;
  2. /**
  3. * 本类说明:
  4. * 测试 开启多线程实现异步的方式
  5. * @author yuanhai
  6. * @date 2022年03月06日
  7. */
  8. public class ThreadTest {
  9. public static void main(String[] args) {
  10. /**
  11. * 创建和初始化线程的方式:
  12. * 1、继承Thread类;
  13. */
  14. System.out.println("main.start......");
  15. // 方式一:继承Thread类
  16. Thread thread = new Thread01();
  17. thread.start(); // 启动线程
  18. System.out.println("main.end......");
  19. }
  20. public static class Thread01 extends Thread {
  21. @Override
  22. public void run() {
  23. System.out.println("当前线程:"+Thread.currentThread().getId());
  24. int i = 10/2;
  25. System.out.println("运行结果:"+i);
  26. }
  27. }
  28. }

2、实现Runnable接口;

package com.atguigu.gulimall.search.thread;

/**
 * 本类说明:
 * 测试 开启多线程实现异步的方式
 * @author yuanhai
 * @date 2022年03月06日
 */
public class ThreadTest {

    public static void main(String[] args) {
        /**
         * 创建和初始化线程的方式:
         * 2、实现Runnable接口;
         */

        System.out.println("main.start......");

        Runnable01 runnable01 = new Runnable01();
        new Thread(runnable01).start();

        System.out.println("main.end......");


    }

    public static class Runnable01 implements Runnable {
        @Override
        public void run() {
            System.out.println("当前线程:"+Thread.currentThread().getId());
            int i = 10/2;
            System.out.println("运行结果:"+i);
        }
    }

}

3、实现Callable接口+FutureTask,可以拿到返回结果,可以处理异常;

package com.atguigu.gulimall.search.thread;

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

/**
 * 本类说明:
 * 测试 开启多线程实现异步的方式
 * @author yuanhai
 * @date 2022年03月06日
 */
public class ThreadTest {

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        /**
         * 创建和初始化线程的方式:
         * 3、实现Callable接口+FutureTask,可以拿到返回结果,可以处理异常;
         */

        System.out.println("main.start......");

        FutureTask<Integer> futureTask = new FutureTask<>(new Callable01());
        new Thread(futureTask).start();

        // 阻塞等待整个线程执行完成,获取返回结果
        Integer integer = futureTask.get();


        System.out.println("main.end......,返回结果:"+integer);


    }

    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;
        }
    }




}

4、通过线程池的方式;

package com.atguigu.gulimall.search.thread;

import java.util.concurrent.*;

/**
 * 本类说明:
 * 测试 开启多线程实现异步的方式
 * @author yuanhai
 * @date 2022年03月06日
 */
public class ThreadTest {

    public static ExecutorService service = Executors.newFixedThreadPool(10);

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        /**
         * 创建和初始化线程的方式:
         * 4、通过线程池的方式;
         *      给线程池直接提交任务
         */

        //我们以后再业务代码里面,前三种启动线程的方式都不用。【将所有的多线程异步任务都交给线程池执行】
        //当前系统中池只有一两个,每个异步任务,提交给线程池让他自己去执行就行

        System.out.println("main.start......");

        service.execute(new Runnable01());  // execute()没有返回值;
        Future<Integer> integerFuture = service.submit(new Callable01());  // submit()可以获取返回值;


        System.out.println("main.end......");


    }

    public static class Runnable01 implements Runnable {
        @Override
        public void run() {
            System.out.println("当前线程:"+Thread.currentThread().getId());
            int i = 10/2;
            System.out.println("运行结果:"+i);
        }
    }

    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;
        }
    }


}

总结

继承Thread类和实现Runnable接口的方式,不能得到返回值;
实现Callable接口的方式可以获取返回值;
继承Thread类、实现Runnable接口和实现Callable接口的方式都不能控制资源;
线程池的方式可以控制资源,也可以获取执行结果,以及捕获异常;

线程池详解

线程池的创建

方法一:使用Executors工具类:
ExecutorService service = Executors.newFixedThreadPool(_10);
方法二:使用原生的ThreadPoolExecutor类:
ThreadPoolExecutor executor = new ThreadPoolExecutor
(_);
着重来看下方法二,原生的方式。创建时,new ThreadPoolExecutor();需要传参数,最多要传七个参数,这七个参数就是线程池的七大参数。

线程池的七大参数

七大参数


1)corePoolSize:核心线程数;
线程池创建好以后就准备就绪的线程数量,就等待来接受异步任务去执行;
只要线程池不销毁,那么池中一直存在这些数量的线程,即使线程空闲。
除非设置了 allowCoreThreadTimeOut 属性,这个属性是允许核心线程超时;
2)maximumPoolSize:最大线程数量;
池中允许的最大的线程数;用来控制资源;
3)keepAliveTime:存活时间;
当线程数大于核心线程数的时候,线程在最大多长时间没有接到新任务就会终止释放,最终线程池维持在 corePoolSize 大小;
如果当前的线程数量大于corePoolSize,释放空闲的线程(maximumPoolSize-corePoolSize);
什么时候释放? 只要线程空闲时间大于指定的keepAliveTime,就会释放;
4)unit:时间单位;
用于设定存活时间 keepAliveTime 的时间单位;
5)BlockingQueue workQueue:阻塞队列;
用来存储等待执行的任务,如果当前对线程的需求超过了 corePoolSize大小,就会放在这里等待空闲线程执行;
即:如果任务有很多,就会将目前多的任务放在队列里面,只要有线程空闲,就会去队列里面取出新的任务继续执行;
6)threadFactory:线程的创建工厂; 比如指定线程名称等;
7)RejectedExecutionHandler handler:拒绝策略;
如果线程满了(队列+线程池都满了),线程池就会使用拒绝策略,按照我们指定的拒绝策略拒绝执行任务;

七大参数的执行顺序(线程池运行流程)

工作顺序:
1、线程池创建,准备好核心线程数量的核心线程,准备接受任务;
2、新的任务进来,用 核心线程core 准备好的空闲线程执行;
2.1、如果core满了,就将再进来的任务放入阻塞队列中;
如果核心线程core有空闲,就会自己去阻塞队列获取任务执行;
2.2、阻塞队列满了,就直接开新线程执行,但是新线程最大只能开到 最大线程数量maximumPoolSize 指定的数量;
2.3、如果max满了,就用RejectedExecutionHandler拒绝任务;
2.4、如果max没满,都执行完成,有很多空闲了,
则在指定的存活时间keepAliveTime以后,释放maximumPoolSize-corePoolSize这些线程
细节:
1)阻塞队列可以选用new LinkedBlockingDeque<>();
它阻塞队列能保存的任务数量默认是Integer的最大值,这可能会内存不够;
所以要传入我们业务定制的数量,比如压力测试得到的结果等,比如 new LinkedBlockingDeque<>(100000);
2)线程工厂可以使用 Executors.defaultThreadFactory();
3)拒绝策略 可以使用 new ThreadPoolExecutor.AbortPolicy());
例1:

 ThreadPoolExecutor executor = new ThreadPoolExecutor(5,
                200,
                10,
                TimeUnit.SECONDS,
                new LinkedBlockingDeque<>(100000),
                Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.AbortPolicy());

例2:
一个线程池 core 7; max 20 ,queue:50,100并发进来怎么分配的;
7个会立即得到执行,50个会进入队列,再开13个进行执行。剩下的30个就使用拒绝策略。
拒绝策略一般都是丢弃:AbortPolicy;如果不想抛弃还要执行,拒绝策略可以使用CallerRunsPolicy;

常见的四种线程池

1、newCachedThreadPool
创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若 无可回收,则新建线程。
2、newFixedThreadPool
创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
3、newScheduledThreadPool
创建一个定长线程池,支持定时及周期性任务执行。
4、newSingleThreadExecutor
创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务 按照指定顺序(FIFO, LIFO, 优先级)执行。

Executors.newCachedThreadPool();  // core是0,所有都可回收
Executors.newFixedThreadPool();  // 固定大小,core=max;都不可回收
Executors.newScheduledThreadPool();  // 定时任务的线程池
xecutors.newSingleThreadExecutor(); // 单线程的线程池,后台从队列里面获取任务,挨个执行

开发中为什么要使用线程池

降低资源的消耗:
通过重复利用已经创建好的线程降低线程的创建和销毁带来的损耗;
提高响应速度:
因为线程池中的线程数没有超过线程池的最大上限时,有的线程处于等待分配任务的状态,当任务来时无需创建新的线程就能执行;
提高线程的可管理性:
线程池会根据当前系统特点对池内的线程进行优化处理,减少创建和销毁线程带来的系统开销。无限的创建和销毁线程不仅消耗系统资源,还降低系统的稳定性,所以使用线程池进行统一分配;

CompletableFuture解决异步编排问题

CompletableFuture使用场景及简介

前面我们已经知道,开发中使用线程池来做多线程相关处理,但是多线程场景下,还会面临一个问题: 在业务复杂情况下,一 个异步调用可能会依赖于另一个异步调用的执行结果 。
比如:
查询商品详情页的逻辑比较复杂,有些数据还需要远程调用,必然需要花费更多的时间:
image.png
同时,1、2、3可以异步执行; 4、5必须 要等待1执行结束后才能执行; 4 、5、6可以异步执行;
这时,就需要使用CompletableFuture来实现。

CompletableFuture启动异步任务

1 创建异步对象

CompletableFuture提供了四个静态方法,来创建一个异步操作:
image.png
下面我们就来创建一个异步对象:
例,使用runAsync()方法,来创建一个异步对象,这里我们传入自己定义的线程池:

@Slf4j
public class ThreadTest {

    public static ExecutorService executor = Executors.newFixedThreadPool(10);

    public static void main(String[] args) {

        System.out.println("main.start......");
        CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
            System.out.println("当前线程:" + Thread.currentThread().getId());
            int i = 10 / 2;
            System.out.println("运行结果:" + i);
        }, executor);
        System.out.println("main.end......");

    }
}

例,使用supplyAsnc()方法,来创建一个异步对象,这里传入自己定义的线程池:

public class ThreadTest {

    public static ExecutorService executor = Executors.newFixedThreadPool(10);

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

        System.out.println("main.start......");

        CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> {
            System.out.println("当前线程:" + Thread.currentThread().getId());
            int i = 10 / 2;
            System.out.println("运行结果:" + i);
            return i;
        }, executor);
        Integer integer = future.get();
        System.out.println("--获取线程运行返回的结果:"+integer);

        System.out.println("main.end......");

    }
}

这里,可以通过supplyAsync()返回的CompletableFuture对象的get()方法,拿到线程运行到返回值;

2 完成回调与异常感知

image.png
whenCompete虽然能得到异常信息,但是没法修改放回数据,而exceptionally可以感知异常,同时返回默认值;

例:使用whenCompete, whenCompete(t,action)方法中,参数t,是运行结果,action是出现的异常:

public class ThreadTest {

    public static ExecutorService executor = Executors.newFixedThreadPool(10);

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

        System.out.println("main.start......");
        CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> {
            System.out.println("当前线程:" + Thread.currentThread().getId());
            int i = 10 / 2;
            System.out.println("运行结果:" + i);
            return i;
        }, executor).whenComplete((res, exception) -> {
            System.out.println("----异步任务成功完成了,结果是:"+res+";异常是:"+exception);
        });


        System.out.println("main.end......");

    }
}

此时的运行结果:

main.start…… 当前线程:12 运行结果:5 ——异步任务成功完成了,结果是:5;异常是:null main.end……

例2,出现异常的情况:

public class ThreadTest {

    public static ExecutorService executor = Executors.newFixedThreadPool(10);

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

        System.out.println("main.start......");

        CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> {
            System.out.println("当前线程:" + Thread.currentThread().getId());
            int i = 10 / 0;
            System.out.println("运行结果:" + i);
            return i;
        }, executor).whenComplete((res, exception) -> {
            System.out.println("----异步任务成功完成了,结果是:"+res+";异常是:"+exception);
        });


        System.out.println("main.end......");

    }
}

运行结果:

main.start…… 当前线程:12 ——异步任务成功完成了,结果是:null;异常是:java.util.concurrent.CompletionException: java.lang.ArithmeticException: / by zero main.end……

例3,使用exceptionally感知异常,并修改返回结果:

public class ThreadTest {

    public static ExecutorService executor = Executors.newFixedThreadPool(10);

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

        System.out.println("main.start......");

        CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> {
            System.out.println("当前线程:" + Thread.currentThread().getId());
            int i = 10 / 0;
            System.out.println("运行结果:" + i);
            return i;
        }, executor).whenComplete((res, exception) -> {
            //虽然能得到异常信息,但是没法修改返回数据。
            System.out.println("----异步任务成功完成了,结果是:"+res+";异常是:"+exception);
        }).exceptionally(throwable -> {
            //可以感知异常,同时返回默认值
            return 10;   //  当结果出现异常,返回一个默认值
        });

        Integer integer = future.get();
        System.out.println("---最终得到的结果:"+integer);

        System.out.println("main.end......");

    }
}

3 handle方法(方法执行完成后的处理)

image.png
handle相关方法可以对方法执行完成后进行处理,无论是成功完成,还是失败完成;
例:

public class ThreadTest {

    public static ExecutorService executor = Executors.newFixedThreadPool(10);

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

        System.out.println("main.start......");

        /**
         * 方法完成后的处理
         */
        CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> {
            System.out.println("当前线程:" + Thread.currentThread().getId());
            int i = 10 / 0;
            System.out.println("运行结果:" + i);
            return i;
        }, executor).handle((res, thr) -> {
            if (res != null) {
                return res * 2;
            }
            if (thr != null) {
                return 0;
            }
           return 0;
        });

        Integer integer = future.get();
        System.out.println("---最终得到的结果:"+integer);

        System.out.println("main.end......");

    }
}

运行结果:

main.start…… 当前线程:12 —-最终得到的结果:0 main.end……

4 线程串行化方法

image.png
image.png
/*
线程串行化
1)、thenRun:不能获取到上一步的执行结果,无返回值
.thenRunAsync(() -> {
System.out.println(“任务2启动了…”);
}, executor);
2)、thenAcceptAsync;能接受上一步结果,但是无返回值
3)、thenApplyAsync:;能接受上一步结果,有返回值
*/

例1:,使用thenRunAsync():

public class ThreadTest {

    public static ExecutorService executor = Executors.newFixedThreadPool(10);

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

        System.out.println("main.start......");

        CompletableFuture<Void> future = CompletableFuture.supplyAsync(() -> {
            System.out.println("当前线程:" + Thread.currentThread().getId());
            int i = 10 / 4;
            System.out.println("运行结果:" + i);
            return i;
        }, executor).thenRunAsync(() -> {
            System.out.println("---任务2启动了");
        }, executor);

        System.out.println("main.end......");
}

例2,使用thenAcceotAsync():

public class ThreadTest {

    public static ExecutorService executor = Executors.newFixedThreadPool(10);

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

        System.out.println("main.start......");

        CompletableFuture<Void> future = CompletableFuture.supplyAsync(() -> {
            System.out.println("当前线程:" + Thread.currentThread().getId());
            int i = 10 / 4;
            System.out.println("运行结果:" + i);
            return i;
        }, executor).thenAcceptAsync( res -> {
            System.out.println("---任务2启动了:"+res);
        },executor);

        System.out.println("main.end......");
}

例3,使用thenApplyAsync() :

public class ThreadTest {

    public static ExecutorService executor = Executors.newFixedThreadPool(10);

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

        System.out.println("main.start......");

        CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
            System.out.println("当前线程:" + Thread.currentThread().getId());
            int i = 10 / 4;
            System.out.println("运行结果:" + i);
            return i;
        }, executor).thenApplyAsync(res -> {
            System.out.println("---任务2启动了:" + res);
            return "hello" + res;
        }, executor);

        System.out.println("main.end......"+future.get());
}

5 两任务组合之两个任务都要完成

image.png

public class ThreadTest {

    public static ExecutorService executor = Executors.newFixedThreadPool(10);

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

        System.out.println("main.start......");

        /**
         * 两个都完成
         */
        CompletableFuture<Integer> future01 = CompletableFuture.supplyAsync(() -> {
            System.out.println("任务1线程:" + Thread.currentThread().getId());
            int i = 10 / 4;
            System.out.println("任务1结束:" + i);
            return i;
        }, executor);

        CompletableFuture<String> future02 = CompletableFuture.supplyAsync(() -> {
            System.out.println("任务2线程:" + Thread.currentThread().getId());
            System.out.println("任务2结束:" );
            return "Hello";
        }, executor);

        // runAfterBothAsync()
//        future01.runAfterBothAsync(future02,() -> {
//            System.out.println("---任务3开始");
//        },executor);
        // thenAcceptBothAsync()
//        future01.thenAcceptBothAsync(future02,(f1, f2) -> {
//            System.out.println("---任务3开始,之前的结果: f1:"+f1+",f2:"+f2);
//        },executor);
        // thenCombineAsync()
        CompletableFuture<String> future = future01.thenCombineAsync(future02, (f1, f2) -> {
            return f1 + ":" + f2 + " -> Haha";
        }, executor);



        System.out.println("main.end......");

    }
}

6 两任务组合之有一个完成就执行任务3

image.png

7 多任务组合

image.png
使用案例
参考gulimall的gulimall-product模块下的ItemController:
image.png
参照SkuInfoService的item()方法;

首先,SkuInfoService的item()方法,使用了异步编排之前的代码如下:

 @Override
    public SkuItemVo item(Long skuId) {

        SkuItemVo skuItemVo  = new SkuItemVo();

        //1、sku基本信息获取  pms_sku_info
        SkuInfoEntity infoEntity = getById(skuId);
        skuItemVo.setInfo(infoEntity);
        Long spuId = infoEntity.getSpuId();
        Long catalogId = infoEntity.getCatalogId();

        //2、sku的图片信息  pms_sku_images
        List<SkuImagesEntity> images = skuImagesService.getImagesBySkuId(skuId);
        skuItemVo.setImages(images);

        //3、获取spu的销售属性组合。
        List<SkuItemSaleAttrVo> saleAttrVos = skuSaleAttrValueService.getSaleAttrsBySpuId(spuId);
        skuItemVo.setSaleAttr(saleAttrVos);

        //4、获取spu的介绍  pms_spu_info_desc
        SpuInfoDescEntity spuInfoDescEntity = spuInfoDescService.getById(spuId);
        skuItemVo.setDesp(spuInfoDescEntity);

        //5、获取spu的规格参数信息。
        List<SpuItemAttrGroupVo> attrGroupVos = attrGroupService.getAttrGroupWithAttrsBySpuId(spuId, catalogId);
        skuItemVo.setGroupAttrs(attrGroupVos);

        return skuItemVo;
    }

下面,使用异步编排来改造:
第一步,我们需要定义一个我们在项目中使用的线程池 MyThreadCongfig:

package com.atguigu.gulimall.product.config;

import org.springframework.context.annotation.Configuration;

import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
 * 本类说明:
 * 线程池配置类
 * @author yuanhai
 * @date 2022年04月14日
 */
@Configuration
public class MyThreadConfig {

    public ThreadPoolExecutor threadPoolExecutor () {
        return new ThreadPoolExecutor(50,
                200,
                10,
                TimeUnit.SECONDS,
                new LinkedBlockingDeque<>(100000),
                Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.AbortPolicy());
    }

}

当然,这样写是不严谨的,应当将线程池中的参数改为可配置的,将核心线程数,最大线程数等,放到配置类中,作为属性引入进来:
首先,定义一个线程池属性类ThreadPoolConfigProperties:

package com.atguigu.gulimall.product.config;

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

/**
 * 本类说明:
 *
 * @author yuanhai
 * @date 2022年04月14日
 */
@ConfigurationProperties(prefix = "gulimall.thread")   //  通过此注解,与配置文件绑定
@Component
@Data
public class ThreadPoolConfigProperties {

    private Integer coreSize;
    private Integer maxSize;
    private Integer keepAliveTime;

}

然后,在配置文件中,配置线程池属性:

gulimall.thread.core-size=20
gulimall.thread.max-size=200
gulimall.thread.keep-alive-time=10

然后,在线程池配置类中使用:

package com.atguigu.gulimall.product.config;

import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
* 本类说明:
* 线程池配置类
* @author yuanhai
* @date 2022年04月14日
*/
// 如果没有把ThreadPoolConfigProperties通过@Component注解加到容器中,就要通过@EnableConfigurationProperties注解开启这个类的配置
//@EnableConfigurationProperties(ThreadPoolConfigProperties.class)
@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());
    }

}

最后,异步编排后的SkuInfoService的item()方法:

// 注入我们自己自定义的线程池 
@Autowired
ThreadPoolExecutor executor; 

@Override
    public SkuItemVo item(Long skuId) throws ExecutionException, InterruptedException {

        SkuItemVo skuItemVo  = new SkuItemVo();

        CompletableFuture<SkuInfoEntity> infoFuture = CompletableFuture.supplyAsync(() -> {
            //1、sku基本信息获取  pms_sku_info
            SkuInfoEntity infoEntity = getById(skuId);
            skuItemVo.setInfo(infoEntity);
            return infoEntity;
        }, executor);

        CompletableFuture<Void> saleAttrFuture = infoFuture.thenAcceptAsync((res) -> {
            //3、获取spu的销售属性组合。
            List<SkuItemSaleAttrVo> saleAttrVos = skuSaleAttrValueService.getSaleAttrsBySpuId(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.setDesp(spuInfoDescEntity);
        }, executor);

        CompletableFuture<Void> baseAttrFuture = infoFuture.thenAcceptAsync(res -> {
            //5、获取spu的规格参数信息。
            List<SpuItemAttrGroupVo> attrGroupVos = attrGroupService.getAttrGroupWithAttrsBySpuId(res.getSpuId(), res.getCatalogId());
            skuItemVo.setGroupAttrs(attrGroupVos);
        }, executor);

        //2、sku的图片信息  pms_sku_images
        CompletableFuture<Void> imageFuture = CompletableFuture.runAsync(() -> {
            List<SkuImagesEntity> images = skuImagesService.getImagesBySkuId(skuId);
            skuItemVo.setImages(images);
        }, executor);

        //等到所有任务都完成
        CompletableFuture.allOf(saleAttrFuture,descFuture,baseAttrFuture,imageFuture).get();


        return skuItemVo;
    }