ExecutorService最佳实践
ExecutorService 是 Java 并发编程中管理线程池的核心工具。下面从设计原则、常见用法、配置建议、陷阱与最佳实践几个维度,给你一份实战级最佳实践总结。
一、核心原则(先记住这 4 条)
- 不要手动创建线程,用线程池
- 线程池必须显式关闭
- 根据业务类型选择线程池
- 任务必须有界、可中断、可失败处理
二、推荐创建方式(不要用 Executors.*)
❌ 不推荐(面试和实战都扣分)
ExecutorService executor = Executors.newFixedThreadPool(10);
原因:
- 队列无界(
LinkedBlockingQueue)→ 可能 OOM - 无法定制拒绝策略
- 隐藏线程池真实行为
✅ 推荐:使用 ThreadPoolExecutor
ExecutorService executor = new ThreadPoolExecutor(
4, // corePoolSize
8, // maximumPoolSize
60L, TimeUnit.SECONDS, // keepAliveTime
new ArrayBlockingQueue<>(100), // 有界队列
new ThreadPoolExecutor.CallerRunsPolicy() // 拒绝策略
);
三、线程池参数如何设置(核心问题)
1️⃣ 线程数怎么定?
| 任务类型 | 建议 |
|---|---|
| CPU 密集型 | CPU核心数 + 1 |
| IO 密集型 | CPU核心数 * 2 或更多 |
| 混合型 | 拆分线程池 |
int cores = Runtime.getRuntime().availableProcessors();
2️⃣ 队列大小怎么定?
✅ 必须有界
new ArrayBlockingQueue<>(500)
❌ 无界队列 = 风险
3️⃣ 拒绝策略怎么选?
| 策略 | 场景 |
|---|---|
AbortPolicy(默认) |
重要任务,快速失败 |
CallerRunsPolicy |
削峰、保护系统 |
| 自定义 | 记录日志 / 落盘 / 报警 |
new RejectedExecutionHandler() {
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
log.warn("task rejected");
}
}
四、正确关闭线程池(非常重要)
✅ 标准关闭模板
executor.shutdown(); // 不再接受新任务
try {
if (!executor.awaitTermination(30, TimeUnit.SECONDS)) {
executor.shutdownNow(); // 强制关闭
}
} catch (InterruptedException e) {
executor.shutdownNow();
}
五、任务提交方式怎么选?
✅ 不同场景用不同方法
| 方法 | 是否关心结果 |
|---|---|
execute() |
不关心 |
submit() |
关心 |
invokeAll() |
批量 |
invokeAny() |
取最快 |
submit + Future 最佳实践
Future future = executor.submit(() -> {
Thread.sleep(1000);
return "ok";
});
try {
String result = future.get(2, TimeUnit.SECONDS);
} catch (TimeoutException e) {
future.cancel(true);
}
✅ 永远给 get() 设置超时
六、异常处理(90% 的人会踩坑)
❌ 错误示例
executor.submit(() -> {
throw new RuntimeException("error");
});
异常会被吞掉!
✅ 正确做法
方式 1:try-catch
executor.submit(() -> {
try {
doWork();
} catch (Exception e) {
log.error("task error", e);
}
});
方式 2:Future.get()
Future> f = executor.submit(task);
f.get(); // 会抛异常
七、线程池命名(排查问题必备)
new ThreadFactory() {
private final AtomicInteger count = new AtomicInteger(1);
public Thread newThread(Runnable r) {
return new Thread(r, "biz-pool-" + count.getAndIncrement());
}
}
或使用 Guava:
ThreadFactory factory = new ThreadFactoryBuilder()
.setNameFormat("order-pool-%d")
.build();
八、常见反模式(❌ 避免)
| 反模式 | 说明 |
|---|---|
| 一个 JVM 一个线程池 | 不同业务混在一起 |
| 任务无限阻塞 | 耗尽线程 |
| 提交后不管 | 丢 Future |
| 线程池不关闭 | 应用无法优雅退出 |
用 Timer |
用 ScheduledExecutorService |
九、典型业务线程池拆分示例
ExecutorService ioExecutor; // IO 任务
ExecutorService cpuExecutor; // 计算任务
ExecutorService asyncExecutor; // 异步通知
✅ 不同 SLA 的任务,必须隔离线程池
十、一句话总结(面试版)
使用
ThreadPoolExecutor创建线程池,
设置合理的核心线程数、有界队列和拒绝策略,
正确关闭线程池,
提交任务时处理异常并设置超时,
不同业务使用不同线程池。
如果你愿意,我可以:
- ✅ 给你一份 生产级线程池配置模板
- ✅ 帮你 分析现有线程池是否合理
- ✅ 讲 ScheduledExecutorService 最佳实践
- ✅ 对比 线程池 vs CompletableFuture
直接告诉我你的使用场景即可。