• SpringBoot下@EnableAsync与@Async异步任务的使用


    参考:

    https://www.cnblogs.com/tsangyi/p/13303018.html

    https://www.cnblogs.com/dudou/p/15136180.html

    一、前言

    我们在使用多线程的时候,往往需要创建Thread类,或者实现Runnable接口,如果要使用到线程池,我们还需要来创建Executors。

    在使用spring中,已经给我们做了很好的支持。只需要添加 @EnableAsync 就可以使用多线程。使用 @Async 就可以定义一个线程任务。通过spring给我们提供的ThreadPoolTaskExecutor就可以使用线程池。

    默认情况下,Spring将搜索相关的线程池定义:要么在上下文中搜索唯一的TaskExecutor bean,要么搜索名为“taskExecutor”的Executor bean。如果两者都无法解析,则将使用SimpleAsyncTaskExecutor来处理异步方法调用。


    业务需求:

      比如用户在下单完成的时候,有两个任务同时执行,分别是发快递和给用户发送短信两个事情要做,我们知道在Java中代码都是依次执行的,比如发送快递需要3秒钟,发送短信需要1秒钟。

    这两个事件没有先前先后的关系,那么完全可以两个事情一起做,异步任务就是来解决这个问题的!

    二、代码实现

    目录结构:

    maven依赖

    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-api</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

     1、定义线程池异步任务配置类:com.zhixi.config

    application.properties

    # 异步线程配置
    # 配置核心线程数
    async.executor.thread.core_pool_size=5
    # 配置最大线程数
    async.executor.thread.max_pool_size=5
    # 配置队列大小
    async.executor.thread.queue_capacity=99999
    # 配置线程池中的线程的名称前缀
    async.executor.thread.name.prefix=async-service-

    线程池配置类:ThreadPoolTaskConfig

    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.scheduling.annotation.EnableAsync;
    import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
    
    import java.util.concurrent.ThreadPoolExecutor;
    
    /**
     * @ClassName ThreadPoolTaskConfig
     * @Author zhangzhixi
     * @Description 线程池配置类
     * @Date 2022-3-29 19:53
     * @Version 1.0
     */
    @Configuration
    @EnableAsync
    public class ThreadPoolTaskConfig {
    
        private static final Logger logger = LoggerFactory.getLogger(ThreadPoolTaskConfig.class);
    
        @Value("${async.executor.thread.core_pool_size}")
        private int corePoolSize;
    
        @Value("${async.executor.thread.max_pool_size}")
        private int maxPoolSize;
    
        @Value("${async.executor.thread.queue_capacity}")
        private int queueCapacity;
    
        @Value("${async.executor.thread.name.prefix}")
        private String namePrefix;
    
        @Bean(name = "taskExecutor")
        public ThreadPoolTaskExecutor asyncServiceExecutor() {
    
            logger.info("start asyncServiceExecutor");
    
            ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
            //配置核心线程数
            executor.setCorePoolSize(corePoolSize);
            //配置最大线程数
            executor.setMaxPoolSize(maxPoolSize);
            //配置队列大小
            executor.setQueueCapacity(queueCapacity);
            //配置线程池中的线程的名称前缀
            executor.setThreadNamePrefix(namePrefix);
    
            // rejection-policy:当pool已经达到max size的时候,如何处理新任务
            // 拒绝策略:CALLER_RUNS:不在新线程中执行任务,而是有调用者所在的线程来执行
            executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
    
            //执行初始化
            executor.initialize();
            return executor;
        }
    }

     2、定义业务接口:com.zhixi.service

    发送短信:AsyncEmailService

    /**
     * @ClassName AsyncService
     * @Author zhangzhixi
     * @Description 发送短信业务
     * @Date 2022-3-29 20:08
     * @Version 1.0
     */
    public interface AsyncEmailService {
        /**
         * 发送短信
         */
        void executeAsync();
    }
    

    发送快递:syncCommodityService

    /**
     * @ClassName AsyncCommodityService
     * @Author zhangzhixi
     * @Description 发送快递的任务
     * @Date 2022-3-29 20:44
     * @Version 1.0
     */
    public interface AsyncCommodityService {
    
        /**
         * 发送快递
         */
        void expressDelivery();
    }
    

    3、业务接口实现类:com.zhixi.service.impl

    短信接口实现类:AsyncEmailServiceImpl

    import com.zhixi.service.AsyncEmailService;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.scheduling.annotation.Async;
    import org.springframework.stereotype.Service;
    
    import java.util.concurrent.TimeUnit;
    
    /**
     * @ClassName AsyncServiceImpl
     * @Author zhangzhixi
     * @Description
     * @Date 2022-3-29 20:08
     * @Version 1.0
     */
    @Service
    public class AsyncEmailServiceImpl implements AsyncEmailService {
    
        private static final Logger logger = LoggerFactory.getLogger(AsyncEmailServiceImpl.class);
    
        @Override
        @Async("taskExecutor")
        public void executeAsync() {
            logger.info("发送短信事件开始执行~");
            logger.info("发送短信中……");
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            logger.info("发送短信事件执行完毕");
        }
    }

    发送快递接口实现类:AsyncCommodityServiceImpl

    import com.zhixi.service.AsyncCommodityService;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.scheduling.annotation.Async;
    import org.springframework.stereotype.Service;
    
    import java.util.concurrent.TimeUnit;
    
    /**
     * @ClassName AsyncCommodityServiceImpl
     * @Author zhangzhixi
     * @Description
     * @Date 2022-3-29 20:46
     * @Version 1.0
     */
    @Service
    public class AsyncCommodityServiceImpl implements AsyncCommodityService {
    
        private static final Logger logger = LoggerFactory.getLogger(AsyncCommodityServiceImpl.class);
    
        @Async("taskExecutor")
        @Override
        public void expressDelivery() {
            logger.info("发送快递事件开始执行~");
            logger.info("发送快递中……");
            try {
                TimeUnit.SECONDS.sleep(3);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            logger.info("发送快递事件执行完毕");
        }
    }
    

    4、视图访问层:com.zhixi.controller  

    AsyncController

    import com.zhixi.service.AsyncCommodityService;
    import com.zhixi.service.AsyncEmailService;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    /**
     * @ClassName MyController
     * @Author zhangzhixi
     * @Description
     * @Date 2022-3-29 20:27
     * @Version 1.0
     */
    @RestController
    public class AsyncController {
    
        @Autowired
        private AsyncEmailService emailService;
    
        @Autowired
        private AsyncCommodityService commodityService;
    
        @RequestMapping(value = "/async")
        public void async() {
            /*寄快递业务方法*/
            commodityService.expressDelivery();
            /*发送短信业务方法*/
            emailService.executeAsync();
        }
    }
    

    5、测试结果  

    浏览器访问:http://localhost:8080/async

    6、第二种配置线程池的方法

    直接实现AsyncConfigurer接口,重写getAsyncExecutor方法即可

    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler;
    import org.springframework.aop.interceptor.SimpleAsyncUncaughtExceptionHandler;
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.scheduling.annotation.AsyncConfigurer;
    import org.springframework.scheduling.annotation.EnableAsync;
    import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
    
    import java.util.concurrent.Executor;
    import java.util.concurrent.ThreadPoolExecutor;
    
    /**
     * @ClassName ThreadPoolTaskConfig
     * @Author zhangzhixi
     * @Description 线程池配置类
     * @Date 2022-3-29 19:53
     * @Version 1.0
     */
    @Configuration
    @EnableAsync
    public class ThreadPoolTaskConfig implements AsyncConfigurer {
    
        private static final Logger logger = LoggerFactory.getLogger(ThreadPoolTaskConfig.class);
    
        @Value("${async.executor.thread.core_pool_size}")
        private int corePoolSize;
    
        @Value("${async.executor.thread.max_pool_size}")
        private int maxPoolSize;
    
        @Value("${async.executor.thread.queue_capacity}")
        private int queueCapacity;
    
        @Value("${async.executor.thread.name.prefix}")
        private String namePrefix;
    
        @Bean(name = "taskExecutor")
        @Override
        public Executor getAsyncExecutor() {
            logger.info("start asyncServiceExecutor");
    
            ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
            //配置核心线程数
            executor.setCorePoolSize(corePoolSize);
            //配置最大线程数
            executor.setMaxPoolSize(maxPoolSize);
            //配置队列大小
            executor.setQueueCapacity(queueCapacity);
            //配置线程池中的线程的名称前缀
            executor.setThreadNamePrefix(namePrefix);
            // rejection-policy:当pool已经达到max size的时候,如何处理新任务
            // 拒绝策略:CALLER_RUNS:不在新线程中执行任务,而是有调用者所在的线程来执行
            executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
    
            //执行初始化
            executor.initialize();
            return executor;
        }
        
    
        @Override
        public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
            return new SimpleAsyncUncaughtExceptionHandler();
        }
    }

    三、@Async失效场景

    • 一、异步方法使用static修饰
    • 二、异步类没有使用@Component注解(或其他注解)导致spring无法扫描到异步类
    • 三、异步方法不能与异步方法在同一个类中
    • 四、类中需要使用@Autowired@Resource等注解自动注入,不能自己手动new对象
    • 五、如果使用SpringBoot框架必须在启动类中增加@EnableAsync注解
    • 六、在Async 方法上标注@Transactional是没用的。 在Async 方法调用的方法上标注@Transactional 有效。
    • 七、调用被@Async标记的方法的调用者不能和被调用的方法在同一类中不然不会起作用!!!!!!!
    • 八、使用@Async时要求是不能有返回值的不然会报错的 因为异步要求是不关心结果的
  • 相关阅读:
    EF Code First列名 'Discriminator' 无效的问题
    并行编程
    通过http上下文判断是否是Ajax请求
    桌面或文件夹里单击鼠标右键新建菜单下不显示文本文档的处理方法
    Frameset框架集的应用
    定时帧(基于定时器的动画 11.1)
    自定义缓冲函数(缓冲 10.2)
    动画速度(缓冲 10.1)
    手动动画(9.2 图层时间)
    CAMediaTiming`协议(9.1 图层时间)
  • 原文地址:https://www.cnblogs.com/zhangzhixi/p/16075524.html
Copyright © 2020-2023  润新知