离线消息推送依赖线程池高效执行I/O密集型任务,核心是匹配负载特征:设corePoolSize为20–50、maxPoolSize为100–200,配SynchronousQueue或容量500–2000的LinkedBlockingQueue,拒策优选CallerRunsPolicy,并联动会话状态校验与实时监控。

在即时通讯系统中,离线消息推送是保障消息可靠性的关键环节,而线程池正是支撑高并发、低延迟推送的底层执行载体。它不负责“决定推什么”,而是高效、可控地“把消息发出去”。用好线程池,核心在于匹配推送场景的负载特征——不是越大越好,也不是越小越省,而是让线程数、队列策略和拒绝策略协同服务于“稳定送达”这个目标。
推送任务要轻量且异步化
离线消息推送本质上是一系列 I/O 密集型操作(查库、序列化、写网络通道),而非 CPU 密集计算。因此线程池不宜配置为 CPU 核心数的倍数,而应侧重吞吐与响应平衡:
- 避免在推送线程中同步调用数据库或 Redis —— 改为先批量读取离线消息(如用 IN 查询或管道命令),再交由线程池分发;
- 每个推送任务只做“发起一次 WebSocket sendText 或 Socket write”,不等待 ACK(ACK 应由回调或单独监听线程处理);
- 使用 session.getAsyncRemote().sendText() 而非同步 API,防止单个慢连接阻塞整个线程。
线程池参数需贴合推送峰值特征
假设系统需在 1 秒内完成 5000 条离线消息的首次投递(例如用户上线触发批量拉取),线程池配置应体现“短时爆发+资源可控”:
- 核心线程数(corePoolSize):设为 20–50,保障日常低峰期轻量推送不创建/销毁线程;
- 最大线程数(maxPoolSize):设为 100–200,应对突发上线潮,但必须配合队列容量限制,防 OOM;
- 工作队列(BlockingQueue):推荐 SynchronousQueue(无缓冲,直接移交)或 LinkedBlockingQueue(容量设为 500–2000),避免消息在内存中堆积过久;
- 拒绝策略(RejectedExecutionHandler):不选 AbortPolicy(抛异常中断流程),建议用 CallerRunsPolicy(由提交线程自己执行),或自定义策略将任务暂存到 Redis 延迟队列重试。
与会话生命周期联动,避免无效调度
推送前必须确认目标用户当前是否真正“在线”——否则线程白跑,还可能因 Session 已失效导致 NPE 或连接异常:
立即学习“Java免费学习笔记(深入)”;
- 推送前查缓存(如 ConcurrentHashMap<userId, WebSocketSession> 或 Redis 的 Hash 结构),不存在则跳过,不入线程池;
- 对已查得的 Session,调用 session.isOpen() 做二次校验,仅对有效会话提交任务;
- 若推送失败(如 IOException),记录日志并触发降级逻辑(如转存为待重推队列),不要让失败任务持续占用线程。
监控与弹性回收不可少
线程池不是“设完就忘”的黑盒,尤其在离线消息场景下,其运行状态直接反映系统健康度:
- 暴露 getActiveCount()、getQueue().size()、getCompletedTaskCount() 等指标到 Prometheus;
- 当队列积压超阈值(如 >300),自动触发告警,并可动态调小 corePoolSize 防雪崩;
- 空闲线程(keepAliveTime)建议设为 60 秒,避免长周期低负载下线程常驻浪费资源。


















