线程池核心思想
凌晨 2 点,你收到报警:服务器 CPU 100%,OOM 了。
查了半天发现,代码里每来一个请求就 new Thread().start(),结果请求一多,线程数爆炸,内存耗尽。
线程池就是为了解决这个问题。
为什么需要线程池
想象没有线程池的场景:
java
// ❌ 每个任务创建一个线程
for (Request req : requests) {
new Thread(() -> process(req)).start();
}
// 1万个请求 = 1万个线程 = 服务器原地爆炸线程的创建和销毁是有代价的:
创建线程的开销:
1. 分配内存(Thread 对象、栈空间默认 1MB)
2. 调用操作系统 API 创建线程
3. JVM 注册线程、初始化本地存储
销毁线程的开销:
1. 释放栈内存
2. 通知操作系统销毁
3. JVM 清理线程数据而线程池的思路是:复用线程,用完不销毁,放回池子里。
线程池 vs 传统多线程
┌────────────────────────────────────────────────────┐
│ 传统多线程 │
│ │
│ 请求1 ──► Thread-1 ──► 销毁 │
│ 请求2 ──► Thread-2 ──► 销毁 │
│ 请求3 ──► Thread-3 ──► 销毁 │
│ ... │
│ 每次都要创建和销毁! │
└────────────────────────────────────────────────────┘
┌────────────────────────────────────────────────────┐
│ 线程池 │
│ │
│ ┌─────────────────────────────────────┐ │
│ │ 线程池(固定大小) │ │
│ │ Thread-1 ──► 复用 ──► Thread-1 │ │
│ │ Thread-2 ──► 复用 ──► Thread-2 │ │
│ │ Thread-3 ──► 复用 ──► Thread-3 │ │
│ └─────────────────────────────────────┘ │
│ │
│ 请求来了,从池子里拿线程用; │
│ 用完了归还到池子里。 │
└────────────────────────────────────────────────────┘ThreadPoolExecutor 七大参数
java
public ThreadPoolExecutor(
int corePoolSize, // 核心线程数
int maximumPoolSize, // 最大线程数
long keepAliveTime, // 空闲存活时间
TimeUnit unit, // 时间单位
BlockingQueue<Runnable> workQueue, // 任务队列
ThreadFactory threadFactory, // 线程工厂
RejectedExecutionHandler handler // 拒绝策略
)参数详解
| 参数 | 说明 |
|---|---|
| corePoolSize | 核心线程数,池中保持的最小线程数,即使空闲也不回收 |
| maximumPoolSize | 最大线程数,池中允许的最大线程数 |
| keepAliveTime | 非核心线程空闲后的存活时间 |
| unit | keepAliveTime 的时间单位 |
| workQueue | 存放待执行任务的队列 |
| threadFactory | 创建线程的工厂,可自定义线程名称 |
| handler | 任务拒绝时的处理器 |
线程创建规则
任务提交
│
├─► 核心线程未满 ──► 创建核心线程执行
│
├─► 核心线程已满 ──► 任务入队
│
├─► 队列已满 ──► 创建临时线程执行
│
└─► 临时线程已达上限 ──► 执行拒绝策略实际执行流程
java
public void execute(Runnable command) {
int c = ctl.get();
// 1. 核心线程未满,创建核心线程
if (workerCountOf(c) < corePoolSize) {
if (addWorker(command, true))
return;
c = ctl.get();
}
// 2. 核心线程满了,尝试加入队列
if (isRunning(c) && workQueue.offer(command)) {
int recheck = ctl.get();
if (!isRunning(recheck) && remove(command))
reject(command);
else if (workerCountOf(recheck) == 0)
addWorker(null, false); // 确保有线程处理
return;
}
// 3. 队列满了,尝试创建临时线程
else if (!addWorker(command, false))
// 4. 线程数已满,执行拒绝策略
reject(command);
}常用队列类型
| 队列 | 特点 | 适用场景 |
|---|---|---|
| LinkedBlockingQueue | 无界或有界链表队列 | 通用 |
| ArrayBlockingQueue | 有界数组队列 | 需要容量限制 |
| SynchronousQueue | 不存储任务,直接提交 | 线程数固定 |
| PriorityBlockingQueue | 按优先级排序 | 任务调度 |
Executors 工厂方法
虽然阿里巴巴规范强烈不推荐直接用 Executors 创建线程池,但还是要知道怎么用:
java
// 1. 固定大小线程池
ExecutorService fixed = Executors.newFixedThreadPool(5);
// core = max = 5,适合 CPU 密集型
// 2. 单线程线程池
ExecutorService single = Executors.newSingleThreadExecutor();
// 保证顺序执行
// 3. 缓存线程池
ExecutorService cached = Executors.newCachedThreadPool();
// core = 0, max = Integer.MAX_VALUE,按需创建
// 4. 调度线程池
ScheduledExecutorService scheduled = Executors.newScheduledThreadPool(5);
// 支持定时和周期性任务阿里巴巴规范警告
【强制】线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。
为什么?
java
// ❌ FixedThreadPool:队列无界,可能 OOM
Executors.newFixedThreadPool(10);
// 内部:new LinkedBlockingQueue<>() 无界!
// ❌ CachedThreadPool:最大线程数无界,可能创建过多线程
Executors.newCachedThreadPool();
// 内部:maximumPoolSize = Integer.MAX_VALUE!推荐做法:
java
// ✅ 显式指定参数
ExecutorService executor = new ThreadPoolExecutor(
10, // 核心线程数
20, // 最大线程数
60L, TimeUnit.SECONDS, // 空闲存活时间
new LinkedBlockingQueue<>(100), // 有界队列
new ThreadFactory() { // 自定义线程名
private AtomicInteger count = new AtomicInteger(1);
@Override
public Thread newThread(Runnable r) {
Thread t = new Thread(r);
t.setName("Worker-" + count.getAndIncrement());
return t;
}
},
new ThreadPoolExecutor.AbortPolicy() // 拒绝策略
);线程池状态
RUNNING ──► SHUTDOWN ──► STOP ──► TIDYING ──► TERMINATED| 状态 | 说明 |
|---|---|
| RUNNING | 接受新任务,处理队列任务 |
| SHUTDOWN | 不接受新任务,但处理队列任务 |
| STOP | 不接受新任务,不处理队列任务,中断正在执行的任务 |
| TIDYING | 所有任务已终止,workerCount 为 0 |
| TERMINATED | terminate() 执行完成 |
关闭线程池
java
ExecutorService executor = new ThreadPoolExecutor(...);
// 方式一:不再接受新任务,等待已提交任务完成
executor.shutdown();
try {
if (!executor.awaitTermination(60, TimeUnit.SECONDS)) {
executor.shutdownNow(); // 超时后强制终止
}
} catch (InterruptedException e) {
executor.shutdownNow();
Thread.currentThread().interrupt();
}
// 方式二:立即终止,不等待
// executor.shutdownNow();注意事项
- 核心线程默认不会被回收:除非设置
allowCoreThreadTimeOut(true) - 队列大小要合理:太小容易拒绝,太大可能 OOM
- 合理设置拒绝策略:根据业务选择
- 正确关闭线程池:避免资源泄漏
要点回顾
线程池的核心思想是复用线程,通过七大参数控制线程数量和任务队列。
记住创建规则:核心线程 → 队列 → 临时线程 → 拒绝策略。
阿里巴巴规范明确要求不使用 Executors,必须显式创建 ThreadPoolExecutor。
