直接用new thread().start()处理任务会导致频繁创建销毁线程,每次分配1mb栈内存、触发系统调用、耗时5–10ms,高并发下迅速耗尽cpu和内存,引发outofmemoryerror;根本解法是使用合理配置的threadpoolexecutor实现线程复用。

直接用 new Thread().start() 处理任务,每次都会新建线程——分配栈内存(默认 1MB)、注册调度器、触发系统调用,耗时 5–10ms;任务结束又得销毁、回收资源。高并发下这会迅速吃光 CPU、拖垮内存,甚至触发 OutOfMemoryError: unable to create new native thread。根本解法不是“少建几个”,而是彻底不建新线程,靠复用。
用线程池替代手动创建
线程池预先创建一批线程并长期持有,任务来了就交由空闲线程执行,执行完不销毁,继续等下一个任务。相当于服务员不随顾客来去,而是一直在岗服务多个客人。
- 不再写
new Thread(runnable).start() - 改用
executor.execute(runnable)或executor.submit(callable) - 核心实现是
ThreadPoolExecutor,不是Executors.newFixedThreadPool()这类封装(它们用无界队列,容易掩盖背压问题)
关键参数要设对,才能真正复用
复用不是自动发生的,依赖合理配置:
Java项目代码review工具。分析Git变更+完整调用链路上下文,推断业务需求,进行多维度评分和分类汇总,生成完整PRD文档。包含细粒度Java代码审查清单(Null安全、异常处理、Streams、并发、equals/hashCode、资源管理、API设计、性能、MyBatis/ORM、事务边界、SQL/DD...
-
corePoolSize:设为业务平均并发量(如 CPU 密集型 ≈
Runtime.getRuntime().availableProcessors(),IO 密集型可适当放大) -
workQueue:必须用有界队列,比如
new ArrayBlockingQueue(200),避免任务无限堆积 - maximumPoolSize:不宜过大,一般不超过 corePoolSize 的 2–4 倍;设太高会导致非核心线程刚建好就因空闲被回收,白费开销
- keepAliveTime:非核心线程空闲多久后回收,建议 ≥60 秒,避免“建了就扔”
注意线程复用的隐含陷阱
复用带来便利,也引入新风险:
-
ThreadLocal 必须清理:线程被复用,上个任务存的
ThreadLocal可能污染下一个任务,用完务必remove() - 避免阻塞操作混用:DB 查询、HTTP 调用等 IO 任务别和计算任务共用一个池,否则快线程干等慢线程,反而加剧切换
-
拒绝策略要选准:队列满 + 线程达上限时,
DiscardPolicy适合日志类可丢任务,CallerRunsPolicy在 Web 场景慎用(可能卡住 Tomcat 主线程)
特殊场景可考虑更轻量方案
不是所有异步都得走传统线程池:
- 纯计算型小任务:优先用
ForkJoinPool(CompletableFuture默认池就是它),自带工作窃取,CPU 利用率更高 - 需要链式异步流转:用
CompletableFuture.supplyAsync(fn, executor)显式指定线程池,避免误入公共池 - 定时/延迟任务:直接用
ScheduledThreadPoolExecutor,不要自己起守护线程轮询
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










