SpringBoot整合线程池使用@Async

Posted pcdd

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了SpringBoot整合线程池使用@Async相关的知识,希望对你有一定的参考价值。

使用步骤

  1. 自定义application.yml配置项及其值
  2. 创建一个线程池配置类
  3. 创建两个接口OrderService和AsyncOrderService及其实现类
  4. 创建Controller用于测试
  5. 观察控制台打印内容的顺序及效果
    5.1 使用@Async
    5.5 不使用@Async

案例需求:用户下单后,服务端生成订单并保存,然后给用户发短信、邮件通知下单成功
由于短信和邮件发送比较耗时,是秒级别的,所以应采用异步的方式,实现方案有很多,这里使用Spring提供的对ThreadPoolExecutor封装的线程池ThreadPoolTaskExecutor,直接在业务方法上使用注解@Async即可(先用@EnableAsync开启)

线程池ThreadPoolExecutor执行规则如下

1. 自定义application.yml配置项及其值

和创建线程池的参数对应,具体含义见步骤2的成员属性注释,参数根据实际情况自行修改,这里仅作为演示

async:
  # 异步订单任务线程池参数
  order-service:
    core-pool-size: 20
    max-pool-size: 100
    keep-alive-seconds: 10
    queue-capacity: 200
    thread-name-prefix: async-order-service-

2. 编写线程池配置类

@Setter
@ConfigurationProperties(prefix = "async.order-service")
@EnableAsync
@Configuration
public class AsyncOrderServiceConfig 
    /**
     * 核心线程数(默认线程数)
     */
    private int corePoolSize;
    /**
     * 最大线程数
     */
    private int maxPoolSize;
    /**
     * 允许线程空闲时间(单位:默认为秒)
     */
    private int keepAliveSeconds;
    /**
     * 缓冲队列大小
     */
    private int queueCapacity;
    /**
     * 线程池名前缀
     */
    private String threadNamePrefix;

    @Bean
    public ThreadPoolTaskExecutor asyncOrderService() 
        ThreadPoolTaskExecutor threadPoolTaskExecutor = new ThreadPoolTaskExecutor();
        threadPoolTaskExecutor.setCorePoolSize(corePoolSize);
        threadPoolTaskExecutor.setMaxPoolSize(maxPoolSize);
        threadPoolTaskExecutor.setKeepAliveSeconds(keepAliveSeconds);
        threadPoolTaskExecutor.setQueueCapacity(queueCapacity);
        threadPoolTaskExecutor.setThreadNamePrefix(threadNamePrefix);
        // 线程池对拒绝任务的处理策略
        threadPoolTaskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        // 完成任务自动关闭 , 默认为false
        threadPoolTaskExecutor.setWaitForTasksToCompleteOnShutdown(true);
        // 核心线程超时退出,默认为false
        threadPoolTaskExecutor.setAllowCoreThreadTimeOut(true);
        threadPoolTaskExecutor.initialize();
        return threadPoolTaskExecutor;
    

首先通过@EnableAsync开始对异步任务的支持,然后使用@ConfigurationProperties把同类配置信息自动封装成一个实体类。其属性prefix表示application.yml配置文件中配置项的前缀,最后结合Lombok的@Setter保证配置文件的值能够注入到该类对应的属性中

此步其实可省略,因为使用@Async不指定value属性时,SpringBoot默认会创建 SimpleAsyncTaskExecutor线程池来处理异步方法调用
注解@Async定义如下,只有一个value属性(SpringBoot 2.3.7 RELEASE)

3. 创建两个接口OrderService和AsyncOrderService及其实现类

创建订单业务接口

public interface OrderService 
    /**
     * 下订单
     */
    void makeOrder();

    /**
     * 生成订单并保存到数据库
     */
    void saveOrder();

创建异步订单业务接口

public interface AsyncOrderService 
    /**
     * 短信服务
     */
    void sendSms();

    /**
     * 邮件服务
     */
    void sendEmail();

创建实现类,编写简单逻辑代替业务逻辑

@Slf4j
@Service
public class OrderServiceImpl implements OrderService 
    @Autowired
    private AsyncOrderService asyncOrderService;

    @Override
    public void makeOrder() 
        // 发短信
        asyncOrderService.sendSms();
        // 发邮件
        asyncOrderService.sendEmail();
        // 保存订单
        saveOrder();
    

    @Override
    public void saveOrder() 
        log.info("生成订单并保存...");
    

@Slf4j
@Service
public class AsyncOrderServiceImpl implements AsyncOrderService 
    @Async("asyncOrderService")
    @Override
    public void sendSms() 
        try 
            long start = System.currentTimeMillis();
            log.info("开始发送短信");
            // 模拟耗时3s
            TimeUnit.SECONDS.sleep(3);
            log.info("短信发送完毕!");
            log.info("发送短信耗时: ms", System.currentTimeMillis() - start);
         catch (InterruptedException e) 
            e.printStackTrace();
        
    

    @Async("asyncOrderService")
    @Override
    public void sendEmail() 
        try 
            long start = System.currentTimeMillis();
            log.info("开始发送邮件");
            // 模拟耗时2s
            TimeUnit.SECONDS.sleep(2);
            log.info("邮件发送完毕!");
            log.info("发送邮件耗时: ms", System.currentTimeMillis() - start);
         catch (InterruptedException e) 
            e.printStackTrace();
        
    

这里使用了@Async注解,它只有一个String类型的value属性,用于指定一个 Bean 的 Name,类型是 Executor 或 TaskExecutor,表示使用这个指定的线程池来执行异步任务,这里指定为步骤2中线程池配置类asyncOrderService

有几点需要注意下,会使@Async失效

● 如果使用SpringBoot框架必须在启动类中增加@EnableAsync注解
● 异步方法不能与被调用的异步方法在同一个类中
● 异步类没有使用@Component注解(或其他同类注解)导致spring无法扫描到异步类
● 类中需要使用@Autowired或@Resource等注解自动注入,不能自己手动new对象
● 异步方法使用非public或static修饰

4. 创建Controller用于测试

@RestController
public class TestController 
    @Autowired
    private OrderService orderService;

    @PostMapping("/makeOrder")
    public String makeOrder() 
        orderService.makeOrder();
        return "ok";
    

5. 观察使用和不用@Async时,控制台打印日志的顺序

使用调式工具调用接口http://localhost:8080/makeOrder,控制台内容如下

使用@Async
不使用@Async

若需对比动态图查看效果,建议刷新页面后即刻查看,效果最佳
使用@Async运行效果

不使用@Async运行效果

从代码逻辑来看,前者是异步执行,后者是同步执行;
从执行效果来看,使用@Async完成速度明显提高

以上是关于SpringBoot整合线程池使用@Async的主要内容,如果未能解决你的问题,请参考以下文章

Springboot 多线程@Async

springboot隔离@Async异步任务的线程池

#yyds干货盘点# springboot配置@Async异步任务的线程池

SpringBoot项目@Async默认线程池导致OOM问题?

SpringBoot项目@Async默认线程池导致OOM问题?

SpringBoot项目@Async默认线程池导致OOM问题?