redismessagelistenercontainer默认使用simpleasynctaskexecutor,每次新建线程、不复用、无队列,易导致线程爆炸或oom;必须显式配置threadpooltaskexecutor并调用initialize()注入settaskexecutor()。

RedisMessageListenerContainer 默认用哪个线程池
Spring Boot 的 RedisMessageListenerContainer 默认使用单线程的 SimpleAsyncTaskExecutor,它每次收到消息都新建线程,不复用、无队列、不可控——高并发场景下极易 OOM 或线程爆炸。
这不是真正的线程池,只是“线程生成器”,连 ThreadPoolTaskExecutor 的边都没沾上。
@Bean 配置自定义线程池并注入 container
必须显式创建一个 ThreadPoolTaskExecutor Bean,并通过 setTaskExecutor() 注入到容器中。不能只靠 @EnableAsync 或 application.yml 里的 spring.task.execution.* 配置——那些只影响 @Async,对 Redis 监听器完全无效。
- 用
@Bean声明线程池时,建议指定唯一名称(如"redis-listener-executor"),避免被其他配置覆盖 - 务必调用
executor.initialize(),否则启动时报IllegalStateException: ThreadPoolTaskExecutor not initialized -
setCorePoolSize建议设为 CPU 核心数 × 2(如 8 核设 16),setMaxPoolSize可略高(如 32),setQueueCapacity推荐有界值(如 100),防止内存无限堆积 - 拒绝策略推荐
new ThreadPoolExecutor.CallerRunsPolicy():当队列满+线程达上限时,由监听器当前线程(即消息分发线程)同步执行任务,避免丢消息
@Bean
public ThreadPoolTaskExecutor redisListenerExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(16);
executor.setMaxPoolSize(32);
executor.setQueueCapacity(100);
executor.setThreadNamePrefix("redis-listener-");
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
executor.initialize(); // ⚠️ 必须调用
return executor;
}
@Bean
public RedisMessageListenerContainer redisMessageListenerContainer(
RedisConnectionFactory connectionFactory,
MessageListenerAdapter listenerAdapter,
ThreadPoolTaskExecutor redisListenerExecutor) {
RedisMessageListenerContainer container = new RedisMessageListenerContainer();
container.setConnectionFactory(connectionFactory);
container.addMessageListener(listenerAdapter, new PatternTopic("__keyevent@*__:del"));
container.setTaskExecutor(redisListenerExecutor); // ✅ 关键注入点
return container;
}
为什么 setTaskExecutor() 后还要注意 listenerAdapter 的线程安全
MessageListenerAdapter 默认是单例的,所有消息共享同一个实例。如果你在 listener 方法里用了非线程安全对象(比如 SimpleDateFormat、ArrayList 未加锁、或静态可变状态),就会出现数据错乱或 ConcurrentModificationException。
- 避免在 listener 方法中修改共享变量;如需状态,改用
ThreadLocal或方法内局部变量 - 不要在 listener 中直接操作 Spring 容器 Bean 的可变字段(除非该 Bean 是 prototype 且线程隔离)
- 若 listener 方法抛出未捕获异常,
RedisMessageListenerContainer默认会停止消费后续消息——需配合setErrorHandler()显式处理
监听器线程池和 @Async 线程池能共用吗
技术上可以,但不建议混用。
- 共用意味着 Redis 消息处理和业务异步任务竞争同一组线程资源,一旦某类任务耗时长、堆积多,会拖垮另一类
- 监控和调优困难:无法区分是监听器慢还是 @Async 任务慢
- 更稳妥的做法是分离:一个专用于
RedisMessageListenerContainer(命名明确、参数按消息吞吐调)、另一个用于@Async(按业务延迟/并发度调)
真正容易被忽略的是:容器初始化顺序。如果 RedisMessageListenerContainer Bean 创建早于你自定义的线程池 Bean,Spring 会 fallback 到默认的 SimpleAsyncTaskExecutor,且不会报错——得靠日志里是否出现 redis-listener- 前缀线程名来确认是否生效。











