直接 new Thread() 不适合高频异步任务,因频繁创建销毁引发GC压力、内存占用高(默认栈1MB/线程)、缺乏调度与生命周期管理;应使用手动配置的 ThreadPoolExecutor,设合理 core/max 线程数、有界队列及拒绝策略。

为什么直接 new Thread() 不适合高频异步任务
频繁创建销毁线程会触发大量对象分配和 GC 压力,JVM 线程栈默认占 1MB 内存,100 个线程就吃掉上百 MB;更关键的是缺乏统一调度、拒绝策略和生命周期控制。线程池不是“可选优化”,而是 Java 异步任务的基础设施。
核心原则:用 ThreadPoolExecutor 或其封装(如 Executors 工厂)替代裸线程,但要注意 Executors 提供的几种预设线程池在生产环境有明显缺陷:
-
Executors.newFixedThreadPool(n)使用无界队列LinkedBlockingQueue,任务持续堆积会导致 OOM -
Executors.newCachedThreadPool()允许创建无限线程,突发流量下可能耗尽系统资源 -
Executors.newSingleThreadExecutor()本质是单点瓶颈,且同样用无界队列
如何安全配置 ThreadPoolExecutor
推荐手动构造 ThreadPoolExecutor,明确控制四个关键参数:
- corePoolSize:常驻线程数,建议设为 CPU 核心数 + 1(I/O 密集型可适当调高)
-
maximumPoolSize:最大线程数,避免设为
Integer.MAX_VALUE,应结合业务吞吐预估上限 -
workQueue:必须用有界队列,如
new ArrayBlockingQueue,防止内存溢出(1024) -
RejectedExecutionHandler:必须显式指定拒绝策略,不要依赖默认的
AbortPolicy(抛RejectedExecutionException),可选CallerRunsPolicy(由提交线程自己执行)或自定义日志+降级逻辑
示例:
立即学习“Java免费学习笔记(深入)”;
ThreadPoolExecutor executor = new ThreadPoolExecutor(
4, // corePoolSize
16, // maximumPoolSize
60L, // keepAliveTime
TimeUnit.SECONDS,
new ArrayBlockingQueue(256),
new ThreadFactoryBuilder().setNameFormat("async-task-%d").build(),
new ThreadPoolExecutor.CallerRunsPolicy()
);
submit() 和 execute() 的区别不能只看文档
表面看 submit() 返回 Future,execute() 无返回值,但实际差异影响异常处理和任务可见性:
- 用
execute()提交的Runnable,若运行中抛未捕获异常,该异常会直接打印到stderr,且无法被调用方感知 - 用
submit()提交的Runnable或Callable,异常会被包装进Future.get()抛出的ExecutionException中,必须主动调用get()才能暴露 - 如果只是发通知、打日志等“火种型”任务,且不关心结果,用
execute()更轻量;若需结果或异常传播,必须用submit()并配套try-catch+get()
线程池关闭的两个阶段必须分清
停机时不能只调 shutdown() 或只调 shutdownNow():
-
shutdown():停止接收新任务,但会等已提交任务(包括队列中等待的)全部执行完再终止,适合优雅下线 -
shutdownNow():尝试中断所有正在执行的线程,并清空队列返回未执行任务列表,但中断不等于立即停止——只有响应中断的代码(如检查Thread.interrupted()或阻塞在wait()/sleep()/join())才会退出 - 正确做法是先
shutdown(),再配合awaitTermination()等待,超时后才调shutdownNow()强制收尾
常见疏漏:忘记在 finally 或 Spring @PreDestroy 中触发关闭,导致应用假死或容器无法正常重启。








