不能直接 new Thread().start() 是因为频繁创建销毁线程开销大且易失控,ExecutorService 通过线程复用、队列缓冲、拒绝策略和统一关闭实现任务与执行者解耦,避免 OOM 和系统崩溃。

为什么不能直接 new Thread().start() 而要用 ExecutorService
频繁创建销毁线程开销大,且容易失控——比如 100 个请求就起 100 个线程,可能直接打爆系统内存或 CPU。ExecutorService 提供复用、排队、拒绝策略、生命周期控制等能力,本质是把「任务」和「执行者」解耦。
常见错误现象:OutOfMemoryError: unable to create new native thread,往往就是没走线程池、裸写 new Thread() 导致的。
- 线程复用:任务提交后由池中空闲线程执行,避免反复创建销毁
- 队列缓冲:当线程忙时,新任务进入阻塞队列(如
LinkedBlockingQueue),而非立即失败 - 可控拒绝:可配置
RejectedExecutionHandler,比如丢弃、抛异常、交由调用线程执行 - 统一关闭:
shutdown()和awaitTermination()能安全等待已有任务完成
如何选对 Executors 工厂方法(别乱用 newFixedThreadPool)
Executors 提供的静态方法看似方便,但多数有隐患。最典型的是 Executors.newFixedThreadPool(int) —— 它用的是无界队列 LinkedBlockingQueue,一旦任务提交速度持续大于处理速度,队列会无限增长,最终 OOM。
真正该用的,是显式构造 ThreadPoolExecutor,自己控制核心参数:
立即学习“Java免费学习笔记(深入)”;
ThreadPoolExecutor executor = new ThreadPoolExecutor(
4, // corePoolSize
8, // maximumPoolSize
60L, // keepAliveTime
TimeUnit.SECONDS,
new ArrayBlockingQueue(100), // 有界队列,防 OOM
new ThreadPoolExecutor.CallerRunsPolicy() // 拒绝时由提交线程自己执行
);
-
corePoolSize:常驻线程数,即使空闲也不会被回收(除非设置allowCoreThreadTimeOut(true)) -
maximumPoolSize:仅当队列满且当前线程数 - 队列类型决定行为:
ArrayBlockingQueue(有界)、SynchronousQueue(不存任务,直接移交,适合 cached 场景) - 拒绝策略选型:
AbortPolicy(抛RejectedExecutionException)、CallerRunsPolicy(降级到调用方线程执行)更可控
submit() vs execute():返回值和异常处理差异
execute(Runnable) 是 void 方法,任务里抛出的异常会被吞掉(只打日志,调用方完全感知不到);而 submit(Runnable) 或 submit(Callable) 返回 Future,能主动检查异常或获取结果。
请注意以下说明:1、本程序允许任何人免费使用。2、本程序采用PHP+MYSQL架构编写。并且经过ZEND加密,所以运行环境需要有ZEND引擎支持。3、需要售后服务的,请与本作者联系,联系方式见下方。4、本程序还可以与您的网站想整合,可以实现用户在线服务功能,可以让客户管理自己的信息,可以查询自己的订单状况。以及返点信息等相关客户利益的信息。这个功能可提高客户的向心度。安装方法:1、解压本系统,放在
典型踩坑:用 execute() 提交含数据库操作的任务,SQL 异常静默丢失,业务逻辑“看似成功”实则失败。
- 要捕获任务内异常 → 用
submit()+future.get()(注意get()会阻塞) - 只关心是否调度成功 →
execute()更轻量 - 需要返回值 → 必须用
submit(Callable) -
Future.get()抛出的不是原始异常,而是包装在ExecutionException中,需调用getCause()获取根本原因
如何安全关闭线程池(shutdown() 不等于立刻停)
shutdown() 只是停止接收新任务,已提交任务(包括队列里的)仍会继续执行;shutdownNow() 会尝试中断正在运行的线程,并清空队列返回未执行任务列表——但「中断」不等于「终止」,线程是否响应取决于它自身是否检查 Thread.interrupted()。
正确关闭流程必须带超时等待:
executor.shutdown();
try {
if (!executor.awaitTermination(30, TimeUnit.SECONDS)) {
executor.shutdownNow(); // 强制终止
if (!executor.awaitTermination(5, TimeUnit.SECONDS)) {
System.err.println("线程池未正常关闭");
}
}
} catch (InterruptedException e) {
executor.shutdownNow();
Thread.currentThread().interrupt();
}
-
awaitTermination()是阻塞调用,必须在shutdown()后调用 - 不要在 finally 块里无条件
shutdownNow(),可能打断还在跑的关键任务 - 如果任务本身是阻塞 I/O(如 socket.read()),需配合可中断机制(如
Socket.setSoTimeout())才能响应中断
线程池不是一次性的,尤其在 Web 应用中,应作为单例长期持有;反复创建销毁反而增加 GC 压力。真正复杂的地方在于:队列容量、拒绝策略、任务超时设计,这些都得贴着你的业务吞吐模型来调,而不是套个 newFixedThreadPool(10) 就完事。









