Java 里常说的“四种线程池”,通常指 Executors 提供的这 4 种:
newFixedThreadPoolnewSingleThreadExecutornewCachedThreadPoolnewScheduledThreadPool
它们本质上都是对 ThreadPoolExecutor 的封装。
一、为什么要用线程池
如果每来一个任务就创建一个线程,会有几个问题:
- 线程创建和销毁有开销
- 线程过多会抢占 CPU,导致频繁上下文切换
- 容易把内存打满
- 不方便统一管理任务
线程池的作用就是:
- 复用线程
- 控制并发数量
- 管理任务队列
- 提高系统稳定性和性能
二、线程池核心参数先理解
在看四种线程池前,先看 ThreadPoolExecutor 的几个关键参数:
public ThreadPoolExecutor(
int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler
)
含义如下:
corePoolSize:核心线程数,默认会长期保留maximumPoolSize:最大线程数keepAliveTime:非核心线程空闲多久后回收workQueue:任务队列threadFactory:线程工厂,决定线程怎么创建RejectedExecutionHandler:拒绝策略,线程池满了怎么办
三、四种线程池解析
1. FixedThreadPool —— 固定大小线程池
创建方式
ExecutorService executor = Executors.newFixedThreadPool(5);
特点
- 线程数量固定
- 多余任务进入阻塞队列等待
- 适合任务量比较稳定的场景
底层参数本质
new ThreadPoolExecutor(
nThreads,
nThreads,
0L,
TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()
)
关键点
corePoolSize = maximumPoolSize- 使用的是 无界队列
LinkedBlockingQueue
优点
- 线程数量可控
- 不会无限创建线程
- 适合稳定并发任务
缺点
-
因为队列是无界的,如果任务堆积太多,可能导致:
- 内存持续增长
- 最终 OOM
适用场景
- 服务器处理相对稳定的请求
- 后台批量处理任务
- 并发量可预估的业务
2. SingleThreadExecutor —— 单线程线程池
创建方式
ExecutorService executor = Executors.newSingleThreadExecutor();
特点
- 线程池里只有一个线程
- 所有任务按顺序串行执行
- 如果线程挂了,会创建新线程继续执行
底层参数本质
new ThreadPoolExecutor(
1,
1,
0L,
TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()
)
优点
- 保证任务顺序执行
- 避免多线程并发问题
- 适合串行化场景
缺点
- 吞吐量低
- 一个任务执行太久会阻塞后续所有任务
- 同样使用无界队列,也有 OOM 风险
适用场景
- 日志顺序处理
- 消息顺序消费
- 需要保证任务串行执行的场景
3. CachedThreadPool —— 可缓存线程池
创建方式
ExecutorService executor = Executors.newCachedThreadPool();
特点
- 线程数几乎不固定
- 来一个任务,如果有空闲线程就复用,没有就新建线程
- 空闲线程 60 秒后回收
底层参数本质
new ThreadPoolExecutor(
0,
Integer.MAX_VALUE,
60L,
TimeUnit.SECONDS,
new SynchronousQueue<Runnable>()
)
关键点
-
corePoolSize = 0 -
maximumPoolSize = Integer.MAX_VALUE -
队列是
SynchronousQueue- 不存储任务
- 任务必须直接交给线程执行
- 没有空闲线程就创建新线程
优点
- 非常适合短时间大量小任务
- 线程复用能力强
- 灵活性高
缺点
-
最大线程数几乎无限
-
如果任务提交过快,可能疯狂创建线程
-
导致:
- CPU 飙升
- 内存耗尽
- 系统雪崩
适用场景
- 轻量、短时、异步的小任务
- 突发性高并发但任务执行极快的场景
不推荐原因
这是面试里高频点:
newCachedThreadPool()容易因为线程数量无限增长导致系统风险。
4. ScheduledThreadPool —— 定时线程池
创建方式
ScheduledExecutorService executor = Executors.newScheduledThreadPool(3);
作用
支持:
- 延迟执行任务
- 周期性执行任务
示例
延迟执行
executor.schedule(() -> {
System.out.println("3秒后执行");
}, 3, TimeUnit.SECONDS);
周期执行
executor.scheduleAtFixedRate(() -> {
System.out.println("每2秒执行一次");
}, 1, 2, TimeUnit.SECONDS);
底层本质
底层是 ScheduledThreadPoolExecutor,核心是一个延迟队列。
优点
- 支持定时任务
- 比
Timer更强大 - 多线程执行,不会因为一个任务异常导致全部任务停止
缺点
- 如果周期任务执行时间过长,可能影响调度精度
- 如果线程数设置不合理,任务会堆积
适用场景
- 定时同步数据
- 周期性清理缓存
- 延迟任务执行
- 轮询调度任务
四、四种线程池对比
| 线程池 | 线程数量 | 队列类型 | 特点 | 风险 |
|---|---|---|---|---|
| FixedThreadPool | 固定 | LinkedBlockingQueue(无界) | 并发数固定 | 任务堆积可能 OOM |
| SingleThreadExecutor | 1 个 | LinkedBlockingQueue(无界) | 串行执行 | 堆积可能 OOM,吞吐低 |
| CachedThreadPool | 不固定,几乎无限 | SynchronousQueue | 灵活,适合短任务 | 线程暴涨 |
| ScheduledThreadPool | 核心线程固定 | DelayedWorkQueue | 定时/周期任务 | 任务堆积、调度延迟 |
五、面试高频点:为什么不建议直接用 Executors 创建线程池
这是非常重要的点。
阿里巴巴 Java 开发手册里建议:
不允许使用
Executors去创建线程池,而是通过ThreadPoolExecutor手动创建。
原因:
1. FixedThreadPool 和 SingleThreadExecutor
使用无界队列:
LinkedBlockingQueue
任务过多会无限堆积,可能导致 OOM。
2. CachedThreadPool
最大线程数:
Integer.MAX_VALUE
可能创建大量线程,导致系统资源耗尽。
3. ScheduledThreadPool
也可能因为任务堆积导致内存压力过大。
六、实际开发推荐方式
建议手动创建线程池,显式指定参数。
示例:自定义线程池
import java.util.concurrent.*;
public class ThreadPoolDemo {
public static void main(String[] args) {
ThreadPoolExecutor executor = new ThreadPoolExecutor(
4, // 核心线程数
8, // 最大线程数
60, // 空闲存活时间
TimeUnit.SECONDS,
new ArrayBlockingQueue<>(100), // 有界队列
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.CallerRunsPolicy() // 拒绝策略
);
for (int i = 0; i < 10; i++) {
int num = i;
executor.execute(() -> {
System.out.println(Thread.currentThread().getName() + " 执行任务:" + num);
});
}
executor.shutdown();
}
}
七、常见拒绝策略
当线程池满了,任务放不进去时,会触发拒绝策略。
1. AbortPolicy
直接抛异常,默认策略
new ThreadPoolExecutor.AbortPolicy()
2. CallerRunsPolicy
由提交任务的线程自己执行
new ThreadPoolExecutor.CallerRunsPolicy()
3. DiscardPolicy
直接丢弃任务,不报错
new ThreadPoolExecutor.DiscardPolicy()
4. DiscardOldestPolicy
丢弃队列中最老的任务,再尝试提交当前任务
new ThreadPoolExecutor.DiscardOldestPolicy()
八、线程池执行流程
线程池接收任务时,一般按这个顺序处理:
- 如果当前线程数 < 核心线程数,创建核心线程执行
- 否则尝试把任务放入队列
- 如果队列满了,且线程数 < 最大线程数,创建非核心线程执行
- 如果线程数也到上限了,就执行拒绝策略
这个流程是面试常考。