一、什么是线程池?
线程池是预先创建一组可复用的线程,由线程池管理器统一管理线程的生命周期(创建、分配任务、执行、回收),避免频繁创建 / 销毁线程的开销,实现任务与线程的解耦。
简单理解:线程池就像一个 "工人团队",线程是 "工人",任务是 "工作"。提前招聘好工人(创建线程),有工作时分配给工人,工作完成后工人回到团队待命,而非直接解雇(避免重复招聘 / 解雇的成本)。
二、线程池的核心作用
- 降低资源消耗:线程创建 / 销毁需要操作系统分配内核资源(如 PCB、栈空间),频繁操作会消耗大量 CPU / 内存,线程池复用线程,减少此类开销。
- 提高响应速度:任务到达时,无需等待线程创建,直接分配给空闲线程执行,缩短任务等待时间。
- 控制并发数量:避免无限制创建线程导致的系统资源耗尽(如 OOM),通过核心线程数、最大线程数限制并发度。
- 统一管理线程:支持任务队列、拒绝策略、线程空闲超时回收等,便于监控和调优(如统计任务执行效率、线程利用率)。
- 支持任务缓存与调度:通过任务队列缓存待执行任务,支持 FIFO、优先级等调度策略。
三、线程池的核心参数(Java 为例,奠定理解基础)
后续创建方法依赖核心参数,先明确ThreadPoolExecutor的 7 个核心参数(所有 Java 线程池底层均基于此类):
| 参数名 | 作用 |
|---|---|
| corePoolSize | 核心线程数:线程池长期维持的最小线程数(即使空闲也不会回收,除非设置allowCoreThreadTimeOut) |
| maximumPoolSize | 最大线程数:线程池允许创建的最大线程数(核心线程忙 + 队列满时,才会创建临时线程) |
| keepAliveTime | 临时线程空闲超时时间:临时线程(超过核心线程数的线程)空闲超过该时间则被回收 |
| unit | keepAliveTime的时间单位(如TimeUnit.SECONDS) |
| workQueue | 任务队列:缓存待执行任务的队列(如LinkedBlockingQueue、SynchronousQueue) |
| threadFactory | 线程工厂:用于创建线程(可自定义线程名称、优先级、是否守护线程等) |
| handler | 拒绝策略:任务队列满 + 线程数达最大时,如何处理新提交的任务(如丢弃、抛出异常、由提交线程执行) |
四、线程池的四种创建方法(Java 实战,最常用)
Java 通过Executors工具类提供 4 种便捷创建方法(底层均封装ThreadPoolExecutor),同时补充自定义线程池(推荐生产环境使用,避免默认方法的隐患)。
前提:统一任务类(所有示例复用)
定义一个 Runnable 任务,用于模拟业务逻辑:
java
// 模拟业务任务:打印线程名+任务编号,耗时1秒
class MyTask implements Runnable {
private final int taskId;
public MyTask(int taskId) {
this.taskId = taskId;
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + " 执行任务 " + taskId);
try {
Thread.sleep(1000); // 模拟任务耗时
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " 完成任务 " + taskId);
}
}
方法 1:FixedThreadPool(固定线程数线程池)
特点
- 核心线程数 = 最大线程数(无临时线程),线程空闲时不会回收。
- 任务队列是无界队列(
LinkedBlockingQueue),可缓存大量任务。 - 适用于:任务量稳定、需要控制并发度的场景(如服务器接收请求)。
代码实现
java
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class FixedThreadPoolDemo {
public static void main(String[] args) {
// 1. 创建固定线程数线程池(核心线程数=3,最大线程数=3)
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3);
// 2. 提交5个任务(线程池只有3个线程,会复用)
for (int i = 1; i <= 5; i++) {
fixedThreadPool.submit(new MyTask(i)); // 提交任务
}
// 3. 关闭线程池(不再接收新任务,等待已提交任务执行完毕)
fixedThreadPool.shutdown();
}
}
执行结果(关键观察)
-
只有 3 个线程(
pool-1-thread-1/2/3)循环执行 5 个任务,无新线程创建。 -
任务 4、5 会在队列中等待,直到有线程空闲。
pool-1-thread-1 执行任务 1
pool-1-thread-2 执行任务 2
pool-1-thread-3 执行任务 3
pool-1-thread-1 完成任务 1
pool-1-thread-1 执行任务 4
pool-1-thread-2 完成任务 2
pool-1-thread-2 执行任务 5
pool-1-thread-3 完成任务 3
pool-1-thread-1 完成任务 4
pool-1-thread-2 完成任务 5
底层原理(简化)
注意隐患
无界队列可能导致任务堆积过多,最终触发 OOM(内存溢出),生产环境需谨慎使用(或自定义队列大小)。
方法 2:CachedThreadPool(缓存线程池)
特点
- 核心线程数 = 0,最大线程数 =
Integer.MAX_VALUE(理论上无上限)。 - 任务队列是同步队列(
SynchronousQueue):队列无容量,提交任务时必须有线程接收,否则立即创建新线程。 - 临时线程空闲超时时间 = 60 秒:空闲超过 60 秒的线程会被回收。
- 适用于:任务量大但执行时间短、并发度波动大的场景(如临时批量处理小任务)。
代码实现
java
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class CachedThreadPoolDemo {
public static void main(String[] args) {
// 1. 创建缓存线程池(核心线程数=0,最大线程数无上限)
ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
// 2. 提交5个任务(任务执行耗时1秒,会创建5个临时线程)
for (int i = 1; i <= 5; i++) {
cachedThreadPool.submit(new MyTask(i));
}
// 3. 关闭线程池
cachedThreadPool.shutdown();
}
}
执行结果(关键观察)
-
5 个任务同时提交,创建 5 个临时线程(
pool-1-thread-1~5),并行执行。 -
任务执行完毕后,线程空闲 60 秒会被回收。
pool-1-thread-1 执行任务 1
pool-1-thread-2 执行任务 2
pool-1-thread-3 执行任务 3
pool-1-thread-4 执行任务 4
pool-1-thread-5 执行任务 5
pool-1-thread-1 完成任务 1
pool-1-thread-2 完成任务 2
pool-1-thread-3 完成任务 3
pool-1-thread-4 完成任务 4
pool-1-thread-5 完成任务 5
底层原理(简化)
java
// Executors.newCachedThreadPool() 底层代码
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(
0, // 核心线程数=0
Integer.MAX_VALUE, // 最大线程数无上限
60L, // 临时线程空闲超时60秒
TimeUnit.SECONDS,
new SynchronousQueue<Runnable>() // 同步队列(无容量)
);
}
注意隐患
最大线程数无上限,若任务执行时间过长且提交频繁,会创建大量线程,导致 CPU / 内存耗尽,生产环境需控制任务执行时间。
方法 3:SingleThreadExecutor(单线程线程池)
特点
- 核心线程数 = 1,最大线程数 = 1(只有 1 个工作线程)。
- 任务队列是无界队列(
LinkedBlockingQueue),任务按 FIFO 顺序执行。 - 适用于:需要串行执行任务的场景(如日志写入、订单处理,避免并发冲突)。
代码实现
java
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class SingleThreadExecutorDemo {
public static void main(String[] args) {
// 1. 创建单线程线程池(只有1个工作线程)
ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
// 2. 提交5个任务(串行执行,前一个完成才执行下一个)
for (int i = 1; i <= 5; i++) {
singleThreadExecutor.submit(new MyTask(i));
}
// 3. 关闭线程池
singleThreadExecutor.shutdown();
}
}
执行结果(关键观察)
- 只有 1 个线程(
pool-1-thread-1),5 个任务按顺序串行执行。
java
pool-1-thread-1 执行任务 1
pool-1-thread-1 完成任务 1
pool-1-thread-1 执行任务 2
pool-1-thread-1 完成任务 2
pool-1-thread-1 执行任务 3
pool-1-thread-1 完成任务 3
pool-1-thread-1 执行任务 4
pool-1-thread-1 完成任务 4
pool-1-thread-1 执行任务 5
pool-1-thread-1 完成任务 5
底层原理(简化)
java
// Executors.newSingleThreadExecutor() 底层代码
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService(
new ThreadPoolExecutor(
1, 1, // 核心线程数=1,最大线程数=1
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>() // 无界队列
)
);
}
注意隐患
无界队列可能导致任务堆积,触发 OOM,生产环境需自定义队列大小。
方法 4:ScheduledThreadPool(定时 / 周期性线程池)
特点
- 核心线程数固定,最大线程数 =
Integer.MAX_VALUE。 - 支持定时执行 (延迟一段时间后执行)和周期性执行(每隔一段时间重复执行)。
- 任务队列是延迟队列(
DelayedWorkQueue),按任务延迟时间排序。 - 适用于:定时任务(如定时备份数据)、周期性任务(如每隔 5 分钟检查服务状态)。
代码实现(两种核心用法)
java
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
public class ScheduledThreadPoolDemo {
public static void main(String[] args) {
// 1. 创建定时线程池(核心线程数=2)
ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(2);
// 用法1:定时执行(延迟2秒后执行一次任务)
System.out.println("当前时间:" + System.currentTimeMillis() / 1000);
scheduledThreadPool.schedule(
new MyTask(1), // 任务
2, // 延迟时间
TimeUnit.SECONDS // 时间单位
);
// 用法2:周期性执行(延迟1秒后,每隔3秒执行一次任务)
// 注意:周期是"上一次任务结束到下一次任务开始"的间隔
scheduledThreadPool.scheduleAtFixedRate(
new MyTask(2), // 任务
1, // 初始延迟时间
3, // 周期时间
TimeUnit.SECONDS // 时间单位
);
// 3. (可选)运行一段时间后关闭线程池(避免无限循环)
try {
Thread.sleep(10000); // 主线程等待10秒
} catch (InterruptedException e) {
e.printStackTrace();
}
scheduledThreadPool.shutdown();
}
}
执行结果(关键观察)
-
任务 1:延迟 2 秒后执行一次。
-
任务 2:延迟 1 秒后执行,之后每隔 3 秒执行一次(共执行 3 次,因主线程等待 10 秒)。
当前时间:1730000000
pool-1-thread-1 执行任务 2 // 延迟1秒后执行
pool-1-thread-1 完成任务 2
pool-1-thread-2 执行任务 1 // 延迟2秒后执行
pool-1-thread-2 完成任务 1
pool-1-thread-1 执行任务 2 // 上一次完成后隔3秒(总4秒)
pool-1-thread-1 完成任务 2
pool-1-thread-1 执行任务 2 // 再隔3秒(总7秒)
pool-1-thread-1 完成任务 2
底层原理(简化)
java
// Executors.newScheduledThreadPool(corePoolSize) 底层代码
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
return new ScheduledThreadPoolExecutor(corePoolSize);
}
// ScheduledThreadPoolExecutor 构造方法(继承 ThreadPoolExecutor)
public ScheduledThreadPoolExecutor(int corePoolSize) {
super(
corePoolSize,
Integer.MAX_VALUE, // 最大线程数无上限
0, TimeUnit.NANOSECONDS,
new DelayedWorkQueue() // 延迟队列
);
}
注意事项
scheduleAtFixedRate:周期是 "上一次任务开始到下一次任务开始" 的间隔(若任务执行时间超过周期,会立即执行下一次)。scheduleWithFixedDelay:周期是 "上一次任务结束到下一次任务开始" 的间隔(本文示例用此方法,更常用)。
五、生产环境推荐:自定义线程池(避免默认方法隐患)
Executors的 4 种方法存在隐患(无界队列 OOM、最大线程数无上限),阿里 Java 开发手册明确推荐:使用ThreadPoolExecutor手动创建线程池,自定义核心参数和队列大小。
代码实现(带拒绝策略、线程工厂)
java
import java.util.concurrent.*;
public class CustomThreadPoolDemo {
public static void main(String[] args) {
// 1. 自定义线程工厂(可选:设置线程名称、优先级等)
ThreadFactory threadFactory = new ThreadFactory() {
private int count = 1;
@Override
public Thread newThread(Runnable r) {
Thread thread = new Thread(r);
thread.setName("custom-thread-" + count++); // 自定义线程名
thread.setPriority(Thread.NORM_PRIORITY); // 正常优先级
return thread;
}
};
// 2. 自定义拒绝策略(可选:任务队列满+线程数达最大时的处理逻辑)
RejectedExecutionHandler rejectedHandler = new RejectedExecutionHandler() {
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
// 自定义处理:如记录日志、返回友好提示
System.out.println("任务 " + ((MyTask) r).taskId + " 被拒绝(队列满+线程数达最大)");
}
};
// 3. 创建自定义线程池(核心参数按需配置)
ThreadPoolExecutor customThreadPool = new ThreadPoolExecutor(
2, // 核心线程数:2
4, // 最大线程数:4(核心2+临时2)
30, // 临时线程空闲超时:30秒
TimeUnit.SECONDS,
new ArrayBlockingQueue<>(3), // 任务队列:有界队列(容量3)
threadFactory, // 自定义线程工厂
rejectedHandler // 自定义拒绝策略
);
// 4. 提交10个任务(核心2+临时2=4线程,队列3,共能处理7个任务,剩余3个被拒绝)
for (int i = 1; i <= 10; i++) {
customThreadPool.submit(new MyTask(i));
}
// 5. 关闭线程池
customThreadPool.shutdown();
}
}
执行结果(关键观察)
-
线程名是自定义的
custom-thread-1~4。 -
队列容量 3,最大线程数 4,共能处理 7 个任务(4 线程 + 3 队列),任务 8、9、10 被拒绝。
custom-thread-1 执行任务 1
custom-thread-2 执行任务 2
custom-thread-3 执行任务 3
custom-thread-4 执行任务 4
任务 8 被拒绝(队列满+线程数达最大)
任务 9 被拒绝(队列满+线程数达最大)
任务 10 被拒绝(队列满+线程数达最大)
custom-thread-1 完成任务 1
custom-thread-1 执行任务 5(队列第1个任务)
custom-thread-2 完成任务 2
custom-thread-2 执行任务 6(队列第2个任务)
custom-thread-3 完成任务 3
custom-thread-3 执行任务 7(队列第3个任务)
...(后续任务完成)
核心参数配置建议(生产环境)
- 核心线程数:CPU 密集型任务(如计算)→ 核心数 + 1;IO 密集型任务(如数据库查询、网络请求)→ 核心数 ×2。
- 队列:用有界队列(如
ArrayBlockingQueue),避免 OOM,容量按需配置(如 100~1000)。 - 拒绝策略:根据业务选择(如
AbortPolicy抛出异常、CallerRunsPolicy由提交线程执行、DiscardOldestPolicy丢弃最旧任务)。
六、总结
1. 四种创建方法对比
| 方法 | 核心线程数 | 最大线程数 | 任务队列 | 适用场景 | 隐患 |
|---|---|---|---|---|---|
| FixedThreadPool | n | n | 无界队列 | 任务量稳定、控制并发度 | 无界队列 OOM |
| CachedThreadPool | 0 | Integer.MAX_VALUE | 同步队列 | 短任务、并发波动大 | 线程过多导致资源耗尽 |
| SingleThreadExecutor | 1 | 1 | 无界队列 | 串行执行任务 | 无界队列 OOM |
| ScheduledThreadPool | coreSize | Integer.MAX_VALUE | 延迟队列 | 定时 / 周期性任务 | 任务过多导致资源耗尽 |
2. 核心建议
- 入门 / 简单场景:用
Executors的四种方法(快速开发)。 - 生产环境:用
ThreadPoolExecutor自定义线程池(可控、无隐患)。 - 线程池关闭:优先用
shutdown()(优雅关闭),而非shutdownNow()(强制中断任务)。 - 监控:通过
ThreadPoolExecutor的getActiveCount()(活跃线程数)、getQueue().size()(队列任务数)监控线程池状态。