手写 threadpoolexecutor 避坑:corepoolsize 设 cpu 核数+1(cpu 密集)或 2×核数(io 密集),maximumpoolsize 与 core 相等或差 2–4 倍,keepalivetime 用 60l 秒,workqueue 必选有界 arrayblockingqueue(容量 100–1000),threadfactory 要自定义命名,拒绝策略优先 callerrunspolicy 或自定义落库/kafka,shutdown 后必调 awaittermination,监控 activecount、queue.size 和积压量并上报 prometheus。
ThreadPoolExecutor 构造参数怎么填才不踩坑
直接用 Executors 工具类创建线程池(比如 newFixedThreadPool)在生产环境基本等于埋雷——它用的 LinkedBlockingQueue 无界队列,任务积压时会 OOM。必须手写 ThreadPoolExecutor,关键在五个参数的取舍:
-
corePoolSize:常驻线程数,建议设为 CPU 核数 + 1(CPU 密集型)或2 × CPU 核数(IO 密集型),别硬写 10/20 -
maximumPoolSize:最大线程数,和corePoolSize相等可避免动态扩容(更可控);若需弹性,差值不宜超过 2–4 倍,否则线程上下文切换开销反超收益 -
keepAliveTime:非核心线程空闲存活时间,设为60L秒足够,别用 0 或 Long.MAX_VALUE -
workQueue:必须用有界队列,ArrayBlockingQueue最稳妥(容量建议 100–1000,看任务平均耗时和吞吐预估) -
threadFactory:一定要自定义,至少带上业务前缀,比如new NamedThreadFactory("order-processor"),不然线程 dump 里全是pool-1-thread-1,查问题抓瞎
拒绝策略(RejectedExecutionHandler)选哪个
默认的 AbortPolicy 直接抛 RejectedExecutionException,但多数业务不能丢任务。常见选择:
-
CallerRunsPolicy:让提交线程自己执行任务,适合突发流量可接受延迟的场景(比如后台报表生成) - 自定义策略:把任务写入 Redis 队列或 Kafka,再由消费者重试(注意幂等)
- 绝对别用
DiscardPolicy或DiscardOldestPolicy,除非你明确知道丢的是什么、且后果可控
设置方式:
new ThreadPoolExecutor(..., new ThreadPoolExecutor.CallerRunsPolicy())
线程池 shutdown 和 shutdownNow 的区别与时机
不是所有地方都能调 shutdown(),尤其 Spring 管理的 Bean: