线程池异步发邮件的核心是“稳”和“可控”,关键在于合理参数、有界阻塞队列缓冲及统一状态管理;需手动构建ThreadPoolExecutor,避免Executors默认无界队列OOM风险。

用线程池异步发邮件,核心不是“快”,而是“稳”和“可控”——尤其面对海量邮件时,靠 new Thread() 或裸调 @Async 默认配置,极易压垮内存或丢任务。关键在三点:合理线程池参数、用阻塞队列缓冲压力、统一管理发送状态(成功/失败/超时)。
选对线程池类型与参数
别直接用 Executors.newFixedThreadPool(),它底层用无界 LinkedBlockingQueue,队列爆满会 OOM。生产环境推荐手动构建 ThreadPoolExecutor:
- 核心线程数设为 CPU 核心数 × 1.5~2(如 4 核机器设 6~8),保证基础吞吐
- 最大线程数控制在 10~20,避免线程过多引发上下文切换开销
- 队列容量设为有界值(如 200~500),配合拒绝策略使用
- 拒绝策略优先选 CallerRunsPolicy(让调用线程自己执行任务,自然降速)或 AbortPolicy(快速失败,配合重试机制)
用 BlockingQueue 做任务缓冲与背压控制
邮件下发不是“越快越好”,而是“不能压垮系统”。在业务层和线程池之间加一层内存队列,实现削峰填谷:
- 定义单例 MailQueue,内部用 LinkedBlockingQueue(QUEUE_MAX_SIZE = 500)
- Controller 接收请求后只做入队(produce),不直接 submit 给线程池
- 另起一个或多个消费者线程(或固定线程池)持续 poll 队列,再分发给邮件执行线程池
- 队列满时直接拒绝新任务并返回 “系统繁忙”,比让线程池崩溃更友好
统一管理每封邮件的发送状态
发邮件不是“扔进去就完事”,要能查、能追、能补。建议为每封邮件生成唯一 traceId,并记录到状态表或内存 Map 中:
- 提交任务时,用 Future
或 CompletableFuture 包裹,同时存入 ConcurrentHashMap ,key 为 traceId - EmailStatus 包含字段:status(PENDING/SUCCESS/FAILED/TIMEOUT)、sendTime、failReason、retryCount
- 异步执行完成后更新状态;若超时未完成,定时任务扫描 PENDING 超过 30s 的任务,标记为 TIMEOUT 并触发告警
- 提供 /mail/status?traceId=xxx 接口供前端或运维查询实时状态
Spring Boot 下的轻量落地示例
不用复杂中间件,纯 Spring Boot 也能跑稳:
- @EnableAsync 开启异步支持
- @Configuration 定义带名线程池 bean(如 "emailTaskExecutor"),配好 core/max/queue/reject 策略
- @Async("emailTaskExecutor") 注解在 service 方法上,方法必须 public,返回 void 或 CompletableFuture
- 发送逻辑中捕获 MailSendException,记录 error 日志 + 更新 EmailStatus 为 FAILED,并自动触发一次重试(最多 2 次)

















