如何选型ExecutorService
选型 ExecutorService 的核心思路是:先明确任务特征(CPU 型 / IO 型 / 混合型、任务数量、是否有返回结果、是否允许排队、是否要求低延迟),再选择合适的线程池类型和参数。下面给你一个从选型到示例的完整指南。
一、先问自己 4 个关键问题
- 任务是 CPU 密集型还是 IO 密集型?
- 任务是一次性、周期性,还是长期运行?
- 是否需要返回结果(Future)?
- 任务量是否可控?是否允许被拒绝?
二、ExecutorService 的常见实现 & 选型
1️⃣ Executors.newFixedThreadPool
适合:
- 任务量稳定
- 需要控制并发数
- CPU 密集型或 IO 密集型均可
ExecutorService executor = Executors.newFixedThreadPool(4);
✅ 特点:
- 线程数固定
- 无界队列(
LinkedBlockingQueue) - 不会无限创建线程
⚠️ 注意:
- 队列无界 → 任务堆积可能 OOM
- 不适合任务突发增长
✅ 最常用、最稳妥
2️⃣ Executors.newCachedThreadPool
适合:
- 大量短生命周期任务
- 任务量波动大
- IO 密集型
ExecutorService executor = Executors.newCachedThreadPool();
✅ 特点:
- 线程可复用
- 无线程上限(最大
Integer.MAX_VALUE) - 空闲线程 60 秒回收
❌ 风险:
- 高并发下可能创建大量线程
- 容易压垮系统
✅ 适合:轻量、短时任务
3️⃣ Executors.newSingleThreadExecutor
适合:
- 需要顺序执行
- 不希望并发
ExecutorService executor = Executors.newSingleThreadExecutor();
✅ 特点:
- 只有一个线程
- 保证任务按顺序执行
✅ 常见场景:
- 日志写入
- 事件处理
- 状态机
4️⃣ Executors.newScheduledThreadPool
适合:
- 定时任务
- 周期性任务
ScheduledExecutorService executor =
Executors.newScheduledThreadPool(2);
executor.scheduleAtFixedRate(
() -> System.out.println("run"),
0, 1, TimeUnit.SECONDS
);
✅ 替代 Timer(更安全)
三、推荐做法:使用 ThreadPoolExecutor 手动创建 ✅
生产环境强烈推荐
ExecutorService executor = new ThreadPoolExecutor(
4, // corePoolSize
8, // maximumPoolSize
60L, TimeUnit.SECONDS, // keepAliveTime
new ArrayBlockingQueue<>(100),
new ThreadPoolExecutor.CallerRunsPolicy()
);
参数含义速记
| 参数 | 作用 |
|---|---|
| corePoolSize | 常驻线程数 |
| maximumPoolSize | 最大线程数 |
| keepAliveTime | 多余线程存活时间 |
| workQueue | 任务队列 |
| RejectedExecutionHandler | 拒绝策略 |
四、按任务类型选型建议(重点)
✅ CPU 密集型
线程数 ≈ CPU 核心数 或 +1
int n = Runtime.getRuntime().availableProcessors();
ExecutorService executor =
Executors.newFixedThreadPool(n);
✅ IO 密集型
线程数可以远大于 CPU 核心数
经验公式:
线程数 ≈ CPU核心数 * 2 或更多
或:
new ThreadPoolExecutor(
16, 32, 60, TimeUnit.SECONDS,
new ArrayBlockingQueue<>(200)
);
✅ 混合型任务
- 拆分任务
- 或使用不同线程池
五、拒绝策略怎么选?
| 策略 | 说明 |
|---|---|
| AbortPolicy | 抛异常(默认) |
| CallerRunsPolicy | 由调用线程执行(推荐) |
| DiscardPolicy | 直接丢弃 |
| DiscardOldestPolicy | 丢弃最老任务 |
✅ 生产环境常用:
new ThreadPoolExecutor.CallerRunsPolicy()
六、常见错误 ❌
❌ 无脑用 Executors.newCachedThreadPool()
❌ 不关闭线程池
❌ 一个线程池干所有事
❌ 队列无界 + 高并发
七、一句话总结
简单场景用 Executors,生产环境用 ThreadPoolExecutor;CPU 型少线程,IO 型多线程,队列要有界,拒绝策略要明确。
如果你愿意,可以告诉我:
- 你的任务类型(CPU / IO)
- QPS 或并发量
- 是否要求返回结果
我可以直接帮你给出可用的线程池参数配置。