Java 执行器与线程池
Executor 框架如何提交任务、管理线程池与关闭:队列、拒绝策略与生命周期。
#concept
#growing
#dev
#java
[!info] related notes
Java 执行器与线程池
这个概念为什么出现
直接 new Thread 无法统一限流、复用与关闭。ExecutorService 把任务提交与执行策略分离:固定/弹性线程池、队列、拒绝策略与 Future 结果。
[!abstract] 一句话理解 线程池复用工作线程执行提交的任务;必须正确配置队列与拒绝策略,并在关闭时
shutdown/awaitTermination。
最小可运行示例
场景:图片缩略图任务有限并发
import java.util.concurrent.*;
public class ThumbPool {
public static void main(String[] args) throws Exception {
ExecutorService pool = Executors.newFixedThreadPool(2, r -> {
Thread t = new Thread(r);
t.setName("thumb-" + t.getId());
return t;
});
try {
Future<String> f = pool.submit(() -> {
// 模拟生成缩略图
return "thumb-ok";
});
System.out.println(f.get(1, TimeUnit.SECONDS));
} finally {
pool.shutdown();
try {
if (!pool.awaitTermination(1, TimeUnit.SECONDS)) {
pool.shutdownNow();
if (!pool.awaitTermination(1, TimeUnit.SECONDS)) {
System.err.println("pool still running: tasks must cooperate with cancellation");
}
}
} catch (InterruptedException e) {
pool.shutdownNow();
Thread.currentThread().interrupt();
}
}
}
}
建议运行:
javac ThumbPool.java && java ThumbPool
期望输出:
thumb-ok
结合场景再看三个关注点
- submit 返回 Future 可取结果/异常。
- 在 finally 中启动关闭并检查等待结果;任务若不响应中断,即使 shutdownNow 也不保证退出。\
- 工厂方法要懂默认队列:
newFixedThreadPool用无界队列,压力下可能 OOM。
核心概念与准确模型
1. 关键角色
Executor/ExecutorServiceThreadPoolExecutor核心实现- 工作队列、核心/最大线程、拒绝策略
2. 拒绝策略
Abort 抛 RejectedExecutionException;CallerRuns 在提交线程执行(关闭后丢弃),会把负载反馈给调用方;Discard/DiscardOldest 可能静默丢任务。若调用方依赖 Future,静默丢弃可能让它永久等待,必须明确拒绝与结果契约。
3. 生命周期
shutdown 拒绝新任务但保留已提交任务(含队列);awaitTermination 只等待并返回是否已终止;shutdownNow 尝试中断运行中任务并返回未启动任务,不等待终止,也不保证返回任务的 Future 自动变成 cancelled。
4. Callable vs Runnable
- Callable 有返回值且可抛受检异常
边界情况与反直觉行为
- get() 可无限等;get(timeout) 只限制本次等待,超时不取消任务。取消需显式 cancel(true),而停止运行仍依赖任务响应中断。\
- submit 捕获任务异常到 Future,get 时通常包装为 ExecutionException;未观察 Future 会漏报。execute 的未捕获异常路径不同,可到达线程的异常处理器。\
- 错误的最大线程 + 无界队列 = 实际不扩容。
常见误区
[!warning] 常见误区:Executors 工厂无脑用 生产常显式
ThreadPoolExecutor并命名、监控队列长度。
工程实践
- 按业务隔离池(CPU 密集 vs IO)。
- 监控活跃线程、队列、拒绝次数。
- 请求上下文传递(MDC)要包装任务。
- 与虚拟线程:大量阻塞任务可用新模型(见虚拟线程篇)。
本节总结
- 池化执行是默认工程姿势
- 配置队列/拒绝/关闭
- Future 承载结果与错误
自测题
- shutdown 与 shutdownNow 区别?
- 无界队列的主要风险?
参考答案
- shutdown 拒绝新任务,继续处理已提交任务;shutdownNow 尝试中断执行中的任务并返回未启动任务,两者都不是“已终止”的证明。\
- 任务堆积导致内存膨胀,线程数可能不按预期增长。
延伸阅读与资料来源
| 资料 | 类型 | 支撑内容 |
|---|---|---|
| Executor interface | API | 执行器 |
| ThreadPoolExecutor | API | 池参数 |