SpringBoot下@EnableAsync与@Async异步任务的使用
参考:
https://www.cnblogs.com/tsangyi/p/13303018.html
https://www.cnblogs.com/dudou/p/15136180.html
案例演示:https://github.com/zhangzhixi0305/async-thread
一、前言
我们在使用多线程的时候,往往需要创建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时要求是不能有返回值的不然会报错的 因为异步要求是不关心结果的