应手动构造ThreadPoolExecutor而非Executors快捷方法,因其可控制核心线程数、最大线程数、有界队列及拒绝策略;submit()返回Future便于异常感知,execute()更轻量但异常不传播;shutdown()等待任务完成,shutdownNow()尝试中断并清空队列;必须显式shutdown,GC不会自动调用。

ExecutorService 不是拿来直接 new 的,它必须通过 Executors 工厂类或 ForkJoinPool 等具体实现类获得;盲目用 Executors.newFixedThreadPool() 在高并发场景下容易 OOM,这是最常踩的坑。
怎么选合适的线程池创建方式
Java 8+ 推荐优先使用 ThreadPoolExecutor 手动构造,而不是 Executors 的快捷方法。因为后者隐藏了关键参数,比如:
-
newFixedThreadPool(n)底层用的是无界LinkedBlockingQueue,任务持续提交会无限堆积,吃光堆内存 -
newCachedThreadPool()允许创建无限线程(60 秒空闲后回收),突发流量下可能创建成千上万个线程,触发OutOfMemoryError: unable to create native thread -
newSingleThreadExecutor()同样用无界队列,且单点故障风险高
正确做法是明确指定核心线程数、最大线程数、队列容量和拒绝策略:
ThreadPoolExecutor executor = new ThreadPoolExecutor(
2, // corePoolSize
4, // maxPoolSize
60L, // keepAliveTime
TimeUnit.SECONDS,
new ArrayBlockingQueue<>(100), // 有界队列
new ThreadPoolExecutor.CallerRunsPolicy() // 拒绝时由调用线程执行
);
submit() 和 execute() 到底该用哪个
区别不在“能不能扔任务”,而在于返回值和异常处理机制:
立即学习“Java免费学习笔记(深入)”;
-
execute(Runnable):void 方法,不返回结果,任务内抛出的异常会直接打印到 stderr,**不会传播给调用方** -
submit(Runnable)或submit(Callable):返回Future,可主动get()获取结果或捕获异常;未调用get()时,异常仍被静默吞掉
所以如果需要错误感知,务必用 submit() 并显式调用 future.get()(注意 get() 会阻塞);若只是发个日志或异步通知,execute() 更轻量。
shutdown() 和 shutdownNow() 的真实行为差异
两者都只是“发起关闭”,不是“立刻停机”:
-
shutdown():停止接收新任务,但会等所有已提交任务(包括队列里没开始的)执行完才真正终止 -
shutdownNow():尝试中断所有正在运行的线程(调用Thread.interrupt()),并清空队列、返回未执行的任务列表;但中断不一定生效——如果任务没响应中断(比如死循环中没检查Thread.currentThread().isInterrupted()),线程会继续跑
生产环境建议先 shutdown() + awaitTermination() 等待合理时间,超时再 shutdownNow() 强制收尾:
executor.shutdown();
if (!executor.awaitTermination(30, TimeUnit.SECONDS)) {
executor.shutdownNow();
}
为什么一定要手动管理 shutdown,不能靠 GC
ExecutorService 是资源型对象,内部持有线程、队列、定时器等,GC **不会自动调用 shutdown()**。漏关会导致:
- JVM 无法正常退出(非守护线程还在跑)
- 线程泄漏,连接池、定时任务等依赖线程池的组件持续占用资源
- 在 Spring 等容器中,可能引发上下文关闭失败或重复初始化
最佳实践是在 try-with-resources(需包装为 AutoCloseable)或 @PreDestroy / finally 块中调用 shutdown();尤其注意:不要在 finalize() 里关,它不可靠且已被弃用。










