为何要用ExecutorService
使用 ExecutorService 的核心目的,是把“任务的提交”和“任务的执行”解耦,统一管理线程资源,提高程序的可维护性、可扩展性和稳定性。下面从多个角度说明“为什么要用它”。
一、不用 ExecutorService 会怎样?
1️⃣ 自己手动创建线程的问题
new Thread(() -> {
// do something
}).start();
存在的问题:
- 线程数量不可控
- 请求多就无限创建线程 → OOM
- 线程生命周期管理困难
- 无法方便关闭、重启、复用
- 任务与执行耦合
- 提交任务 = 创建线程
- 缺乏统一监控和统计
- 异常处理不友好
二、ExecutorService 解决了什么问题?
✅ 1. 线程复用(避免频繁创建销毁)
ExecutorService executor = Executors.newFixedThreadPool(10);
- 线程池复用线程
- 减少上下文切换
- 提高性能
✅ 2. 控制并发规模(防止系统崩溃)
ExecutorService executor = Executors.newFixedThreadPool(5);
- 最多同时 5 个线程
- 多余任务进入队列等待
- 防止资源耗尽
✅ 3. 解耦任务提交与执行
executor.submit(() -> System.out.println("task"));
- 提交任务 ≠ 立即执行
- 执行策略由线程池决定
- 更符合“职责分离”设计原则
✅ 4. 支持任务返回值(Future)
Future future = executor.submit(() -> 1 + 1);
Integer result = future.get();
- 支持异步计算
- 支持结果获取
- 支持异常传播
✅ 5. 统一生命周期管理
executor.shutdown();
executor.awaitTermination(10, TimeUnit.SECONDS);
- 优雅关闭线程池
- 避免线程泄漏
- 控制应用退出行为
✅ 6. 支持任务队列和拒绝策略
new ThreadPoolExecutor(
2, 4, 60, TimeUnit.SECONDS,
new ArrayBlockingQueue<>(100),
new ThreadPoolExecutor.CallerRunsPolicy()
);
- 队列满怎么办?
- 拒绝策略可配置
- 系统更健壮
三、ExecutorService 的典型使用场景
| 场景 | 原因 |
|---|---|
| Web 服务器处理请求 | 控制并发 |
| 批量任务处理 | 提高吞吐 |
| 定时 / 异步任务 | 统一管理 |
| 后台计算 | 不阻塞主线程 |
| 防止 OOM | 限制线程数 |
四、不用 ExecutorService 的替代方案?
| 方案 | 问题 |
|---|---|
| new Thread | 不可控 |
| Timer | 单线程、异常会终止 |
| ForkJoinPool | 适合特定计算模型 |
| 手动维护线程 | 复杂、易出错 |
五、一句话总结
ExecutorService 是 Java 并发编程中用来“安全、可控、高效地管理线程和任务”的标准工具。
如果你愿意,我可以:
- 对比
ExecutorServicevsCompletableFuture - 讲清楚 线程池参数如何设置
- 结合 实际生产问题 讲为什么必须用线程池