直接 new Thread() 错在频繁创建销毁线程消耗系统资源,导致 OOM 或响应延迟;应使用 ThreadPoolExecutor 精确控制 corePoolSize、maximumPoolSize 等参数实现复用与隔离。

为什么直接 new Thread() 是错的
频繁创建销毁线程会消耗大量系统资源,JVM 需要为每个线程分配栈空间、维护线程状态,还会触发频繁 GC。线程池复用已创建的线程,避免重复开销,同时限制并发数防止系统过载。
常见错误现象:OutOfMemoryError: unable to create new native thread,本质是操作系统线程数超限,而非堆内存不足;或者响应延迟陡增,但 CPU 使用率不高——说明线程在阻塞或空转,没被有效调度。
- 不要在循环里写
new Thread(runnable).start() - 避免用
Executors.newCachedThreadPool()处理不可控任务(比如用户上传脚本),它可能无限创建线程 - 不要把耗时 I/O 操作(如数据库查询、HTTP 调用)和 CPU 密集型任务混在一个线程池里,会互相拖慢
如何选对 ThreadPoolExecutor 构造参数
ThreadPoolExecutor 是最灵活的线程池实现,其余 Executors 工厂方法只是它的封装。关键不是背参数顺序,而是理解每个参数的实际约束力:
-
corePoolSize:常驻线程数,即使空闲也不会被回收(除非设置allowCoreThreadTimeOut(true)) -
maximumPoolSize:线程总数上限,仅当任务队列满且当前线程数 -
workQueue:必须是阻塞队列,LinkedBlockingQueue默认无界,容易掩盖问题;ArrayBlockingQueue有界,配合拒绝策略更可控 -
RejectedExecutionHandler:别用默认的AbortPolicy(抛RejectedExecutionException),生产环境建议自定义,比如记录日志 + 降级为同步执行
示例:处理 Web 请求的 IO 密集型场景
立即学习“Java免费学习笔记(深入)”;
new ThreadPoolExecutor(
4, // core
16, // max
60L, TimeUnit.SECONDS,
new ArrayBlockingQueue<>(100),
new ThreadPoolExecutor.CallerRunsPolicy()
);
submit() 和 execute() 到底该用哪个
execute(Runnable) 只负责提交任务,不返回结果,也不暴露异常;submit() 返回 Future,能获取结果或捕获执行异常,但代价是额外对象分配和同步开销。
- 如果任务无返回值、也不关心是否成功(如发日志、埋点),用
execute() - 需要等结果或做超时控制,必须用
submit(Callable),并显式调用future.get(3, TimeUnit.SECONDS) - 注意:
submit(Runnable)返回的Future调用get()只会返回null,且仍会包装原始异常——这点容易误判
典型陷阱:Future.get() 不设超时会永久阻塞,导致调用线程卡死;未 catch ExecutionException 会让异常吞没在 Future 内部。
线程池 shutdown 的正确姿势
不关闭线程池会导致 JVM 无法退出(因为非守护线程还在运行),Spring Boot 应用中尤其容易漏掉这步。
- 先调
shutdown():停止接收新任务,但会等已有任务完成 - 再调
awaitTermination(long, TimeUnit)等待指定时间,返回false表示仍有活跃任务 - 若超时未结束,可调
shutdownNow()尝试中断正在执行的任务(注意:中断不一定生效,取决于任务是否响应中断)
关键细节:线程池本身不管理内部线程的守护状态,所有线程默认是非守护线程;shutdownNow() 发送的是中断信号,不是强制杀线程,任务代码里必须检查 Thread.interrupted() 或捕获 InterruptedException 才能及时退出。
真正难处理的是那些阻塞在 Socket.read() 或数据库 ResultSet.next() 上的任务——它们不响应中断,需要配合超时机制或连接层配置才能真正释放。










