项目场景:
项目存在发送批量邮件的需求,采用线程池来发送批量邮件
问题描述
配置类开启异步功能
@EnableAsync
配置类中注入一个线程池对象
@Bean("myAsync")
public ThreadPoolTaskExecutor myExecutor(){
ThreadPoolTaskExecutor threadPoolTaskExecutor = new ThreadPoolTaskExecutor();
// 核心线程池大小
threadPoolTaskExecutor.setCorePoolSize(corePoolSize);
// 最大线程数
threadPoolTaskExecutor.setMaxPoolSize(maxPoolSize);
// 队列容量
threadPoolTaskExecutor.setQueueCapacity(queueCapacity);
// 活跃时间
threadPoolTaskExecutor.setKeepAliveSeconds(keepAliveSeconds);
// 线程名字前缀
threadPoolTaskExecutor.setThreadNamePrefix(threadNamePrefix);
// RejectedExecutionHandler:当pool已经达到max-size的时候,如何处理新任务
// CallerRunsPolicy:不在新线程中执行任务,而是由调用者所在的线程来执行
threadPoolTaskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
// 初始化
threadPoolTaskExecutor.initialize();
return threadPoolTaskExecutor;
}
使用SpringBoot注解@Async放在发送邮件的方法上
@Async("myAsync")
public void sendMail(Mail mail){
...
}
出现BUG:使用公司的私服的邮件服务器发送单个邮件需要约30秒,即没有@Async注解使用sendMail方法需要耗时30秒。
使用@Async注解后没有执行发送邮件的操作
原因分析:
主线程没有等待子进程结束返回了,即线程池没有设置等待终止时间
解决方案:
线程池有一个属性awaitTerminationMillis,是主进程等待子进程执行时间
添加等待终止时间属性解决问题,修改后代码如下
@Bean("myAsync")
public ThreadPoolTaskExecutor myExecutor(){
ThreadPoolTaskExecutor threadPoolTaskExecutor = new ThreadPoolTaskExecutor();
// 核心线程池大小
threadPoolTaskExecutor.setCorePoolSize(corePoolSize);
// 最大线程数
threadPoolTaskExecutor.setMaxPoolSize(maxPoolSize);
// 队列容量
threadPoolTaskExecutor.setQueueCapacity(queueCapacity);
// 活跃时间
threadPoolTaskExecutor.setKeepAliveSeconds(keepAliveSeconds);
+ // 主线程等待子线程执行时间
+ threadPoolTaskExecutor.setAwaitTerminationSeconds(awaitTerminationSeconds);
// 线程名字前缀
threadPoolTaskExecutor.setThreadNamePrefix(threadNamePrefix);
// RejectedExecutionHandler:当pool已经达到max-size的时候,如何处理新任务
// CallerRunsPolicy:不在新线程中执行任务,而是由调用者所在的线程来执行
threadPoolTaskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
// 初始化
threadPoolTaskExecutor.initialize();
return threadPoolTaskExecutor;
}
总结
在SpringBoot中使用@Async有许多坑,比如:
1.异步方法用static修饰或者和被调用者在同一个类中会导致注解失效
2.异步方法只能用public方法修饰
3.异步方法返回值只能返回void或Future
TIPS
一.rejectedExectutionHandler参数字段用于配置拒绝策略
AbortPolicy:用于被拒绝任务的处理程序,它将抛出RejectedExecutionException
CallerRunsPolicy:用于被拒绝任务的处理程序,它直接在execute方法的调用线程中运行被拒绝的任务
DiscardOldestPolicy:用于被拒绝任务的处理程序,它放弃最旧的未处理请求,然后重试execute
DiscardPolicy:用于被拒绝任务的处理程序,默认情况下它将丢弃被拒绝的任务
二.线程池的核心线程数设置
一般说来,大家认为线程池的大小经验值应该这样设置:(其中N为CPU processors的个数)
(1)如果是CPU密集型应用,则线程池大小设置为N+1(或者是N),线程的应用场景:主要是复杂算法
(2)如果是IO密集型应用,则线程池大小设置为2N+1(或者是2N),线程的应用场景:主要是:数据库数据的交互,文件上传下载,网络数据传输等等
+1的原因是:即使当计算密集型的线程偶尔由于缺失故障或者其他原因而暂停时,这个额外的线程也能确保CPU的时钟周期不会被浪费。