ExecutorService
ExecutorService 是 Java 并发包(java.util.concurrent)中提供的一个线程池框架接口。它封装了线程的创建、管理、复用以及任务提交等细节,让开发者专注于任务本身,而不是底层线程的生命周期。
1. 为什么需要 ExecutorService?
直接手动创建 Thread 对象存在诸多问题:
- 资源消耗高:每个线程都需要分配栈内存(通常 1MB 左右),创建和销毁开销大。
- 不可控:无限制创建线程会导致系统崩溃。
- 管理困难:无法统一监控、限制并发数、排队任务。
ExecutorService 通过线程池解决了这些问题:
- 复用线程,降低创建/销毁成本。
- 限制最大线程数,防止资源耗尽。
- 提供任务队列,实现提交与执行的解耦。
- 统一管理线程生命周期(关闭、监控等)。
2. 核心功能
2.1 提交任务
execute(Runnable):无返回值,执行Runnable任务。submit(Callable)/submit(Runnable):返回Future对象,可获取执行结果或异常。invokeAll(...)/invokeAny(...):批量提交任务。
2.2 关闭线程池
shutdown():平滑关闭,不再接收新任务,等待已提交任务执行完。shutdownNow():尝试立即停止所有正在执行的任务,并返回未开始的任务列表。awaitTermination(timeout, unit):阻塞等待线程池完全终止。
2.3 监控与状态
isShutdown()/isTerminated():判断关闭或终止状态。
3. 创建 ExecutorService 的常用工厂方法(通过 Executors 工具类)
| 方法 | 说明 | 适用场景 |
|---|---|---|
newFixedThreadPool(int n) | 固定数量的核心线程,任务队列无界 | 负载较重但可控的服务器 |
newCachedThreadPool() | 核心线程数 0,最大线程数很大,空闲线程存活 60 秒 | 短期异步任务,并发量不定 |
newSingleThreadExecutor() | 单线程池,保证任务顺序执行 | 需要顺序处理或单后台线程 |
newScheduledThreadPool(int n) | 支持定时、延迟执行任务 | 周期性的后台任务 |
注意:Executors 提供的某些线程池可能使用无界队列(如 FixedThreadPool),在高负载下可能导致内存溢出。在生产环境中,更推荐直接使用 ThreadPoolExecutor 构造方法显式指定参数。
4. 基本使用示例(Lambda)
import java.util.concurrent.*;
public class ExecutorServiceDemo {
public static void main(String[] args) throws Exception {
// 创建固定大小线程池
ExecutorService executor = Executors.newFixedThreadPool(3);
// 提交 Runnable 任务
executor.execute(() -> System.out.println("Runnable 任务执行"));
// 提交 Callable 任务并获取结果
Future<String> future = executor.submit(() -> {
Thread.sleep(1000);
return "Callable 返回结果";
});
System.out.println(future.get()); // 阻塞直到得到结果
// 提交批量任务
executor.invokeAll(List.of(
() -> { System.out.println("任务1"); return 1; },
() -> { System.out.println("任务2"); return 2; }
));
// 关闭线程池(通常放在 finally 或 try-with-resources 中)
executor.shutdown();
// 等待所有任务完成
if (!executor.awaitTermination(60, TimeUnit.SECONDS)) {
executor.shutdownNow();
}
}
}
5. ExecutorService vs 直接创建 Thread
| 维度 | 直接 new Thread | ExecutorService |
|---|---|---|
| 线程创建开销 | 每次 new 都创建新线程,开销大 | 复用线程,开销小 |
| 资源控制 | 无限制,易导致系统过载 | 可限制最大线程数,有任务队列 |
| 任务管理 | 无法统一管理 | 可监控、取消、等待、批量提交 |
| 关闭 | 需要手动追踪每个线程 | 一键 shutdown,优雅关闭 |
| 返回值 | 仅能通过共享变量或回调 | 支持 Future 获取结果和异常 |
| 定时/延迟 | 需要自己写循环或 Timer | 直接支持 schedule |
6. 注意事项与最佳实践
- 务必关闭线程池:使用
shutdown()或shutdownNow(),避免资源泄漏。可使用 try-with-resources(Java 19+ 虚拟线程?普通线程池需手动关闭)。 - 不要用无界队列:
FixedThreadPool使用无界LinkedBlockingQueue,若任务提交速度远超处理速度,队列会无限膨胀导致 OOM。推荐直接构造ThreadPoolExecutor并指定有界队列和拒绝策略。 - 异常处理:
submit(Runnable)如果任务抛异常,不会打印堆栈,除非通过Future.get()获取;execute(Runnable)会直接抛出异常(但线程池会捕获并记录)。建议统一处理ThreadFactory中的未捕获异常处理器。 - 虚拟线程:Java 21+ 引入了虚拟线程(
Executors.newVirtualThreadPerTaskExecutor()),对于高并发 I/O 密集型任务,虚拟线程比传统线程池更轻量。但仍需理解ExecutorService的概念。
7. 示例:自定义 ThreadPoolExecutor
int corePoolSize = 5;
int maxPoolSize = 10;
long keepAliveTime = 60L;
BlockingQueue<Runnable> workQueue = new ArrayBlockingQueue<>(100);
ThreadFactory threadFactory = Executors.defaultThreadFactory();
RejectedExecutionHandler handler = new ThreadPoolExecutor.CallerRunsPolicy();
ThreadPoolExecutor executor = new ThreadPoolExecutor(
corePoolSize, maxPoolSize, keepAliveTime, TimeUnit.SECONDS,
workQueue, threadFactory, handler
);
总结
ExecutorService 是 Java 多线程编程中管理线程的标准方式。它提供了线程复用、任务队列、生命周期控制等关键能力,远比手动创建 Thread 更安全、高效。在实际项目中,除非极简单的场景,否则都应该使用 ExecutorService(或直接使用 ThreadPoolExecutor)来管理线程。对于 Java 21 以上版本,可以结合虚拟线程获得更好的 I/O 密集型任务性能。