SpringBoot使用线程池发送邮件出现邮件没有发送的BUG及其解决方法

项目场景:

项目存在发送批量邮件的需求,采用线程池来发送批量邮件


问题描述

配置类开启异步功能
@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的时钟周期不会被浪费。
  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值