线程池的类型
11/23/25About 8 min
线程池的类型
在Java中,java.util.concurrent.Executors类提供了几种常用的线程池实现。这些线程池基于ThreadPoolExecutor类构建,针对不同场景进行了参数优化。下面将详细介绍各种线程池的特点、实现原理、适用场景和使用示例。
1. SingleThreadExecutor
特点:
- 创建一个单线程化的线程池,使用唯一的工作线程执行任务
- 保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行
- 当线程异常终止时,会创建一个新线程来替代它
- 适用于需要保证任务顺序执行的场景
源码实现:
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}使用示例:
ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
// 提交任务
for (int i = 0; i < 5; i++) {
final int taskId = i;
singleThreadExecutor.execute(() -> {
System.out.println("Task " + taskId + " is running by " + Thread.currentThread().getName());
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
// 关闭线程池
singleThreadExecutor.shutdown();适用场景:
- 需要按顺序执行任务的场景
- 对任务执行顺序有严格要求的业务逻辑
- 简单的异步处理,避免任务并发执行导致的问题
2. FixedThreadPool
特点:
- 创建一个固定大小的线程池,线程数量一旦达到最大值就不再变化
- 线程空闲时不会被回收,除非线程池被关闭
- 使用无界队列(LinkedBlockingQueue)存储等待执行的任务
- 适用于已知并发压力的场景,对线程资源有严格控制
源码实现:
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}使用示例:
// 创建固定大小为5的线程池
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(5);
// 提交10个任务
for (int i = 0; i < 10; i++) {
final int taskId = i;
fixedThreadPool.execute(() -> {
System.out.println("Task " + taskId + " is running by " + Thread.currentThread().getName());
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
// 关闭线程池
fixedThreadPool.shutdown();适用场景:
- 服务器负载比较重的情况下,限制最大并发数
- 资源有限的场景,需要控制线程数量
- 处理CPU密集型任务,线程数一般设置为CPU核心数
3. CachedThreadPool
特点:
- 创建一个可缓存的线程池,线程数可以动态调整
- 核心线程数为0,最大线程数为Integer.MAX_VALUE
- 线程空闲60秒后会被回收
- 使用SynchronousQueue作为工作队列,它不存储任务,直接传递给工作线程
- 适用于大量短生命周期的异步任务
源码实现:
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}使用示例:
ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
// 提交多个短任务
for (int i = 0; i < 20; i++) {
final int taskId = i;
cachedThreadPool.execute(() -> {
System.out.println("Task " + taskId + " is running by " + Thread.currentThread().getName());
try {
// 短任务,执行时间较短
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
// 关闭线程池
cachedThreadPool.shutdown();适用场景:
- 执行大量短生命周期的异步任务
- 任务执行时间短,且任务提交频率不稳定
- 需要处理突发大量请求的情况
4. ScheduledThreadPool
特点:
- 创建一个定长线程池,支持定时及周期性任务执行
- 核心线程数固定,最大线程数为Integer.MAX_VALUE
- 使用DelayedWorkQueue作为工作队列,支持任务延迟执行和周期性执行
- 适用于需要定时执行任务或周期性执行任务的场景
源码实现:
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
return new ScheduledThreadPoolExecutor(corePoolSize);
}
// ScheduledThreadPoolExecutor的构造方法
public ScheduledThreadPoolExecutor(int corePoolSize) {
super(corePoolSize, Integer.MAX_VALUE, 0, TimeUnit.NANOSECONDS,
new DelayedWorkQueue());
}使用示例:
// 创建核心线程数为3的定时线程池
ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(3);
// 延迟执行任务 - 延迟2秒后执行
System.out.println("提交延迟任务的时间: " + System.currentTimeMillis());
scheduledThreadPool.schedule(() -> {
System.out.println("延迟任务执行时间: " + System.currentTimeMillis());
}, 2, TimeUnit.SECONDS);
// 周期性执行任务 - 延迟1秒后开始,每3秒执行一次
scheduledThreadPool.scheduleAtFixedRate(() -> {
System.out.println("周期性任务执行时间: " + System.currentTimeMillis());
}, 1, 3, TimeUnit.SECONDS);
// 注意:实际使用中需要适当关闭线程池,这里为了看到周期性任务效果暂时不关闭
// scheduledThreadPool.shutdown();适用场景:
- 定时执行任务(如定时备份、定时清理等)
- 周期性执行任务(如定时检查、定时推送等)
- 延迟执行任务
线程池参数详解
所有的线程池实现都基于ThreadPoolExecutor,它有以下核心参数:
public ThreadPoolExecutor(int corePoolSize, // 核心线程数
int maximumPoolSize, // 最大线程数
long keepAliveTime, // 线程空闲时间
TimeUnit unit, // 时间单位
BlockingQueue<Runnable> workQueue, // 工作队列
ThreadFactory threadFactory, // 线程工厂
RejectedExecutionHandler handler) // 拒绝策略参数说明:
- corePoolSize: 线程池的核心线程数,即使线程空闲也不会被回收(除非设置了allowCoreThreadTimeOut)
- maximumPoolSize: 线程池允许的最大线程数
- keepAliveTime: 非核心线程的空闲超时时间
- unit: keepAliveTime的时间单位
- workQueue: 存储等待执行任务的队列
- threadFactory: 创建线程的工厂,可以自定义线程名称等属性
- handler: 当任务无法被执行时的拒绝策略
线程池工作原理
线程池的工作流程如下:
- 当提交一个新任务时,如果当前运行的线程数小于corePoolSize,则创建新的核心线程执行任务
- 如果当前运行的线程数等于corePoolSize,则将任务加入工作队列
- 如果工作队列已满,且当前运行的线程数小于maximumPoolSize,则创建非核心线程执行任务
- 如果工作队列已满,且当前运行的线程数等于maximumPoolSize,则执行拒绝策略
拒绝策略
当线程池无法执行新任务时(工作队列已满且达到最大线程数),会触发拒绝策略:
- AbortPolicy: 默认策略,直接抛出RejectedExecutionException异常
- CallerRunsPolicy: 由调用者线程执行任务
- DiscardPolicy: 直接丢弃任务,不做任何处理
- DiscardOldestPolicy: 丢弃队列中最旧的任务,然后尝试执行新任务
线程池使用的注意事项
避免使用Executors创建线程池:根据阿里巴巴Java开发手册,Executors创建的线程池存在以下问题:
FixedThreadPool和SingleThreadExecutor使用无界队列,可能导致OOMCachedThreadPool和ScheduledThreadPool最大线程数为Integer.MAX_VALUE,可能创建大量线程导致OOM
推荐使用ThreadPoolExecutor自定义线程池:明确核心线程数、最大线程数、工作队列大小和拒绝策略
合理设置线程池参数:
- CPU密集型任务:线程数 = CPU核心数 + 1
- IO密集型任务:线程数 = CPU核心数 * 2
- 混合型任务:根据实际情况调整
线程池必须正确关闭:使用shutdown()或shutdownNow()方法关闭线程池
注意任务异常处理:在线程池中执行的任务,如果抛出未捕获异常,线程会终止并被新线程替代
避免任务长时间阻塞:长时间阻塞的任务会占用线程资源,导致线程池效率下降
监控线程池状态:定期监控线程池的活跃线程数、队列大小等指标
自定义线程池示例
基于上述注意事项,推荐使用ThreadPoolExecutor直接创建线程池:
// 创建自定义线程池
ThreadPoolExecutor executor = new ThreadPoolExecutor(
5, // 核心线程数
10, // 最大线程数
60L, TimeUnit.SECONDS, // 线程空闲时间
new LinkedBlockingQueue<>(100), // 有界队列,容量100
new ThreadFactory() {
private final AtomicInteger threadNumber = new AtomicInteger(1);
@Override
public Thread newThread(Runnable r) {
Thread thread = new Thread(r, "custom-thread-" + threadNumber.getAndIncrement());
thread.setDaemon(false);
thread.setPriority(Thread.NORM_PRIORITY);
return thread;
}
},
new ThreadPoolExecutor.CallerRunsPolicy() // 拒绝策略
);
// 使用线程池
for (int i = 0; i < 20; i++) {
final int taskId = i;
executor.execute(() -> {
System.out.println("Task " + taskId + " is running by " + Thread.currentThread().getName());
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
// 关闭线程池
executor.shutdown();
try {
if (!executor.awaitTermination(60, TimeUnit.SECONDS)) {
executor.shutdownNow();
}
} catch (InterruptedException e) {
executor.shutdownNow();
Thread.currentThread().interrupt();
}总结
线程池类型对比
| 线程池类型 | 核心线程数 | 最大线程数 | 线程空闲时间 | 工作队列 | 特点 | 适用场景 |
|---|---|---|---|---|---|---|
| SingleThreadExecutor | 1 | 1 | 0ms | LinkedBlockingQueue | 单线程执行,保证任务顺序;线程异常终止时会创建新线程替代 | 需要保证任务顺序执行的场景;简单的异步处理 |
| FixedThreadPool | n | n | 0ms | LinkedBlockingQueue | 固定线程数,控制并发;线程空闲时不会被回收 | 服务器负载较重的情况;资源有限的场景;CPU密集型任务 |
| CachedThreadPool | 0 | Integer.MAX_VALUE | 60s | SynchronousQueue | 线程数动态调整;空闲线程60秒后回收;直接传递任务给工作线程 | 大量短生命周期的异步任务;任务执行时间短且提交频率不稳定;突发大量请求的情况 |
| ScheduledThreadPool | corePoolSize | Integer.MAX_VALUE | 0ms | DelayedWorkQueue | 支持定时及周期性任务执行;核心线程数固定 | 定时执行任务(如定时备份、清理);周期性执行任务(如定时检查、推送);延迟执行任务 |
核心结论
- SingleThreadExecutor:单线程执行,保证任务顺序
- FixedThreadPool:固定线程数,控制并发
- CachedThreadPool:灵活调整线程数,适用于短任务
- ScheduledThreadPool:支持定时和周期性任务
在实际应用中,应根据具体场景选择合适的线程池类型,或通过ThreadPoolExecutor自定义线程池,以达到最优的性能和资源利用率。