Skip to content

线程池核心思想

凌晨 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非核心线程空闲后的存活时间
unitkeepAliveTime 的时间单位
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
TERMINATEDterminate() 执行完成

关闭线程池

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();

注意事项

  1. 核心线程默认不会被回收:除非设置 allowCoreThreadTimeOut(true)
  2. 队列大小要合理:太小容易拒绝,太大可能 OOM
  3. 合理设置拒绝策略:根据业务选择
  4. 正确关闭线程池:避免资源泄漏

要点回顾

线程池的核心思想是复用线程,通过七大参数控制线程数量和任务队列。

记住创建规则:核心线程 → 队列 → 临时线程 → 拒绝策略

阿里巴巴规范明确要求不使用 Executors,必须显式创建 ThreadPoolExecutor。

基于 VitePress 构建