Executor 是接口不能直接 new,需通过 Executors 工厂类创建线程池;推荐生产环境手动构建 ThreadPoolExecutor 以精准控制参数与拒绝策略。

Executor 接口本身不能直接 new,得用 Executors 工厂类创建具体实现
很多人看到 Executor 就想 new Executor(),但它是接口,没有构造方法。真正干活的是它的实现类,比如 ThreadPoolExecutor。日常开发几乎不手动 new 它,而是通过 Executors 工厂方法获取预设线程池。
常用方式有:
-
Executors.newFixedThreadPool(n):固定大小线程池,适合负载稳定、任务量可预期的场景 -
Executors.newCachedThreadPool():按需创建线程,空闲 60 秒自动回收,适合大量短时异步任务(但要注意突发流量可能创建过多线程) -
Executors.newSingleThreadExecutor():单线程顺序执行,保证任务串行,适合日志写入、状态更新等需顺序保障的操作 -
Executors.newScheduledThreadPool(n):支持延迟/周期执行,对应ScheduledExecutorService
注意:Executors 创建的线程池在高并发或任务阻塞时容易出问题——比如 newCachedThreadPool 可能 OOM,newFixedThreadPool 的无界队列可能内存溢出。生产环境更推荐直接 new ThreadPoolExecutor,自己控制核心线程数、最大线程数、队列类型和拒绝策略。
submit() 和 execute() 的区别不只是返回值,还涉及异常处理路径
execute(Runnable) 是 Executor 接口定义的方法,只接受 Runnable,不返回结果,也不抛出检查异常;submit() 是 ExecutorService 接口的方法,有三个重载:
立即学习“Java免费学习笔记(深入)”;
-
submit(Runnable)→ 返回Future>,可用于判断是否完成,但 get() 不会抛出任务内异常 -
submit(Runnable, T result)→ 返回Future,get() 返回指定 result -
submit(Callable→ 返回) Future,get() 会**原样抛出任务中未捕获的异常**(包括 RuntimeException)
关键点:如果用 execute() 提交一个抛异常的 Runnable,异常会直接打印到 stderr(由线程的 UncaughtExceptionHandler 处理),主线程完全感知不到;而用 submit(Callable) + future.get(),异常会在 get 调用时重新抛出,可被 try-catch 捕获。
ExecutorService es = Executors.newFixedThreadPool(2);
es.submit(() -> {
throw new RuntimeException("boom");
}); // 异常被吞,只打堆栈
Future> f = es.submit((Callable) () -> {
throw new RuntimeException("boom");
});
try {
f.get(); // 这里才真正抛出异常
} catch (ExecutionException e) {
e.getCause().printStackTrace(); // 原始异常在此
}
shutdown() 和 shutdownNow() 不是“立即停”,它们的行为差异影响资源释放时机
shutdown() 是温和关闭:不再接受新任务,但会等已提交任务(包括队列中等待的任务)全部执行完再终止线程池;shutdownNow() 是强制中断:尝试中断所有正在执行的线程,并清空任务队列,返回尚未执行的任务列表。
注意两点:
- 中断只是「建议」线程停止,能否响应取决于任务自身是否检查
Thread.interrupted()或是否使用了可中断的阻塞操作(如BlockingQueue.take()、Thread.sleep()) - 无论哪种 shutdown,线程池状态变为
SHUTDOWN或STOP后,再次调用submit/execute都会抛RejectedExecutionException - 典型误用:在 finally 块里只调
shutdown(),但没等任务结束就退出 JVM,导致队列中任务丢失
安全做法是 shutdown 后加 awaitTermination:
executor.shutdown();
try {
if (!executor.awaitTermination(30, TimeUnit.SECONDS)) {
executor.shutdownNow(); // 超时后强制中断
}
} catch (InterruptedException e) {
executor.shutdownNow();
Thread.currentThread().interrupt();
}
自定义 ThreadPoolExecutor 时,拒绝策略不是摆设,选错会导致静默丢任务
当线程池满(线程数达 maximumPoolSize 且工作队列也满)时,新任务会触发拒绝策略。JDK 提供四种内置策略:
-
AbortPolicy(默认):抛RejectedExecutionException,调用方必须处理 -
CallerRunsPolicy:由提交任务的线程(比如主线程)自己执行该任务,可缓解压力,但可能拖慢调用方 -
DiscardPolicy:静默丢弃,无日志无提示 -
DiscardOldestPolicy:丢弃队列头任务,然后重试提交当前任务
生产环境别用 DiscardPolicy,尤其在消息处理、支付回调等场景,丢任务等于丢数据。更稳妥的是自定义策略,比如记录日志 + 上报监控 + 降级写 DB 或 MQ 重试队列。
另外,corePoolSize 和 maximumPoolSize 设为相等可避免线程动态伸缩带来的不确定性;队列优先选 LinkedBlockingQueue(无界)或 ArrayBlockingQueue(有界),慎用 SynchronousQueue(它不存储任务,相当于直传,要求 maximumPoolSize > corePoolSize 才有意义)。
线程池命名、拒绝策略日志、任务超时控制、以及是否允许核心线程超时(allowCoreThreadTimeOut),这些细节在压测和线上问题排查时往往比线程数本身更关键。











