文章目录
- SpringBoot异步线程@Async的使用注意
- 一、创建线程池交给Spring管理
- 二、异步线程的调用
- 三、注意点
SpringBoot异步线程@Async的使用注意
当业务需要异步处理的时候(例如异步保存操作日志),我们不能简单的通过new Thread的方式来使用,这样子性能低,重复的创建Thread和回收Thread非常的占用资源,所以我们使用Java的线程池机制,来做到线程的回收利用,线程池的介绍详见我的另一篇文章:java多线程线程池原理剖析
@Async的使用方式如下
一、创建线程池交给Spring管理
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;import java.util.concurrent.Executor;
import java.util.concurrent.ThreadPoolExecutor;/*** @author zhangxing* @date 2021/5/11*/
@Configuration
// 使用该注解,打开SpringBoot对异步的支持
@EnableAsync
public class AsyncConfig {private static final int CORE_POOL_SIZE = 8;private static final int MAX_POOL_SIZE = 16;private static final int QUEUE_CAPACITY = 200;private static final int KEEP_ALIVE_SECONDS = 60;private static final String THREAD_NAME_PREFIX = "LogThreadPool-";@Bean("logThreadPoolTaskExecutor")public Executor logThreadPoolTaskExecutor() {ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();// 核心线程数量executor.setCorePoolSize(CORE_POOL_SIZE);// 最大线程数量executor.setMaxPoolSize(MAX_POOL_SIZE);// 队列中最大任务数executor.setQueueCapacity(QUEUE_CAPACITY);// 线程名称前缀executor.setThreadNamePrefix(THREAD_NAME_PREFIX);// 当达到最大线程数时如何处理新任务// 此处不使用ThreadPoolExecutor.CallerRunsPolicy()的拒绝策略,如果异步线程池的线程不够用,则会使用将任务提交过来的线程处理,即此处的web容器的主线程,禁止该操作,并发高日志写入慢的时候可能会将主线程全部占用,此时用户无法正常访问系统// executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());// 抛弃旧日志的拒绝策略,因为日志可以容忍丢失,所以这里选择丢失最早的没有被处理的任务,如果不能容忍丢失,还需要异步处理,请使用mq中间件或更大的等待队列executor.setRejectedExecutionHandler(new ThreadPoolExecutor.DiscardOldestPolicy());// 线程空闲后最大存活时间executor.setKeepAliveSeconds(KEEP_ALIVE_SECONDS);// 初始化线程池executor.initialize();return executor;}}
二、异步线程的调用
public class OrderServiceImpl {@Autowiredprivate LogServiceImpl logServiceImpl;public void createOrder () {....// 异步保存日志logServiceImpl.saveLog();}
}public class LogServiceImpl {// 此处必须指明需要使用的线程池的beanName@Async("logThreadPoolTaskExecutor")public void saveLog() {// 保存日志}}
三、注意点
- 如果@Async中没有指定线程池,则使用SpringBoot默认生成的线程池,通过打印线程信息,可以通过日志看到线程池的类型为
SimpleAsyncTaskExecutor
,该线程池本质上每来一个新任务都开启新线程,并且没有线程上线,不能起到线程复用的效果,不推荐使用 - 拒绝策略如果使用
CallerRunsPolicy
,那么该异步线程池中的线程不够用时,会将任务还给创建任务的线程来处理,一般正常情况会将任务还给web容器的主线程处理,会影响用户的使用性能,不建议使用 - @Async本质上使用线程池,所以异步逻辑中不能使用安全框架中的上下文获取当前用户信息,因为消息做的是异步处理,所以当需要使用当前用户等信息时,应当将这些信息通过参数的形式传入