ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Java线程池这破玩意,差点让我周末加班排查到凌晨

Java线程池这破玩意,差点让我周末加班排查到凌晨 上周五上线的新服务在凌晨突然开始疯狂告警线程池队列积压导致核心接口超时下游服务雪崩。你猜怎么着线程池的workQueue用了一个无界队列——这玩意儿在流量突增时就是个定时炸弹。一、现象线程池把服务拖垮了背景是个订单风控服务QPS平时200左右高峰期能到800。为了异步处理风控规则我们用了ThreadPoolTaskExecutor核心配置如下Bean public ThreadPoolTaskExecutor riskControlExecutor() { ThreadPoolTaskExecutor executor new ThreadPoolTaskExecutor(); executor.setCorePoolSize(5); executor.setMaxPoolSize(10); executor.setQueueCapacity(100); // 你以为这行有用 executor.setThreadNamePrefix(risk-ctrl-); executor.initialize(); return executor; }上线后风平浪静直到凌晨促销开始——监控显示队列积压了2万多个任务线程数却始终卡在5个核心线程数最终内存飙到90%触发Full GC。这里有个反直觉的点你以为QueueCapacity设置了100就能限制队列长度二、根因Spring的线程池配置陷阱打开ThreadPoolTaskExecutor源码你会发现这个坑// 关键代码如果不显式指定队列类型默认用LinkedBlockingQueue private BlockingQueueRunnable createQueue(int queueCapacity) { return (queueCapacity 0 ? new LinkedBlockingQueue(queueCapacity) : new SynchronousQueue()); }问题出在queueCapacity的默认值是Integer.MAX_VALUE即使你手动设置了setQueueCapacity(100)如果没同时指定setRejectedExecutionHandler当队列满时线程池会直接扩容到maxPoolSize而不会触发拒绝策略。更坑的是maxPoolSize只在队列满时才会生效——但无界队列永远不会满三、正确姿势线程池必须配拒绝策略这才是能扛住流量突增的配置Bean public ThreadPoolTaskExecutor riskControlExecutor() { ThreadPoolTaskExecutor executor new ThreadPoolTaskExecutor(); executor.setCorePoolSize(5); executor.setMaxPoolSize(10); executor.setQueueCapacity(100); executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); // 关键 executor.setThreadNamePrefix(risk-ctrl-); executor.initialize(); return executor; }为什么用CallerRunsPolicyAbortPolicy默认直接抛异常在异步场景容易丢数据DiscardPolicy静默丢弃排查问题时哭都来不及CallerRunsPolicy让提交任务的线程自己执行既能限流又能保证不丢数据压测对比相同2000突发请求配置平均耗时最大内存占用任务丢弃率无界队列默认策略3200ms1.2GB0%有界队列CallerRuns850ms800MB8%四、避坑清单线程池的暗礁队列选型坑CPU密集型用SynchronousQueue避免任务堆积IO密集型用LinkedBlockingQueue需明确设置容量定时任务用DelayedWorkQueue参数动态化用ThreadPoolExecutor的setCorePoolSize()方法可以在运行时调整线程数但最大线程数不支持动态调整需要重建线程池监控埋点必须监控三项指标executor.getActiveCount() // 活跃线程数 executor.getQueue().size() // 队列积压数 executor.getCompletedTaskCount() // 已完成任务线程泄漏永远记得用try-finally包裹任务代码否则一个未捕获的异常会让线程直接消失executor.execute(() - { try { doBusiness(); } finally { log.info(Task completed); // 至少打日志 } });五、结论线程池不是配个参数就能闭眼用的工具——maxPoolSize在有界队列前就是个摆设拒绝策略不配等于自杀。下次你看到线程数不涨但CPU打满时先摸摸队列是不是又偷偷变成无界的了。你在项目里还遇到过哪些线程池的骚操作评论区聊聊你的血泪史。
返回列表