线程池到底怎么管理线程的?从 ThreadPoolExecutor 到拒绝策略全链路拆解

线程池到底怎么管理线程的?从 ThreadPoolExecutor 到拒绝策略全链路拆解

面试常考:核心参数、任务提交链路、Worker 原理、拒绝策略、Executors 陷阱

一、从一个线上事故说起

某服务用 Executors.newFixedThreadPool(200) 处理请求,运行 2 小时后 OOM。原因:newFixedThreadPool 使用无界队列 LinkedBlockingQueue,堆积了数百万任务导致内存溢出。为什么阿里规约禁止用 Executors 创建线程池?线程池的参数到底怎么影响任务流转?

二、线程池七大核心参数

java 复制代码
public ThreadPoolExecutor(
    int corePoolSize,              // 核心线程数
    int maximumPoolSize,           // 最大线程数
    long keepAliveTime,            // 非核心线程空闲存活时间
    TimeUnit unit,                 // 时间单位
    BlockingQueue<Runnable> workQueue,  // 任务队列
    ThreadFactory threadFactory,   // 线程工厂
    RejectedExecutionHandler handler     // 拒绝策略
)
参数 作用 典型值
corePoolSize 常驻线程数 CPU密集: N+1, IO密集: 2N
maximumPoolSize 线程数上限 根据负载设定
keepAliveTime 非核心线程空闲回收时间 60s
workQueue 存放待执行任务 有界队列!
threadFactory 创建线程(命名、优先级) 自定义命名
handler 任务满+线程满时的处理 AbortPolicy

三、任务提交链路全拆解

execute 方法------核心调度逻辑

java 复制代码
public void execute(Runnable command) {
    if (command == null) throw new NullPointerException();
    int c = ctl.get();

    // 1. 当前线程数 < corePoolSize,创建核心线程执行
    if (workerCountOf(c) < corePoolSize) {
        if (addWorker(command, true))
            return;
        c = ctl.get();
    }

    // 2. 线程数 >= core,任务入队列
    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);  // 没有线程了,创建一个
    }

    // 3. 队列满,尝试创建非核心线程
    else if (!addWorker(command, false))
        // 4. 线程数达到 max,执行拒绝策略
        reject(command);
}

任务流转图

scss 复制代码
                    ┌──────────────────┐
                    │  execute(task)   │
                    └────────┬─────────┘
                             │
                    ┌────────▼──────────┐
                    │ 线程数 <          │
                    │ corePoolSize?     │──Yes──▶ addWorker(core)
                    └────────┬──────────┘         创建核心线程执行
                             │ No                  └──▶ return
                    ┌────────▼──────────┐
                    │ 队列未满?         │──Yes──▶ workQueue.offer(task)
                    │                   │         入队等待
                    └────────┬──────────┘         └──▶ return
                             │ No
                    ┌────────▼──────────┐
                    │ 线程数 <          │
                    │ maximumPoolSize?  │──Yes──▶ addWorker(non-core)
                    └────────┬──────────┘         创建非核心线程执行
                             │ No                  └──▶ return
                    ┌────────▼──────────┐
                    │ reject(task)      │  执行拒绝策略
                    └───────────────────┘

关键点:先核心线程 → 再队列 → 再非核心线程 → 最后拒绝。队列优先于非核心线程!

四、ctl 变量:一个 int 存两个值

java 复制代码
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));

// ctl 的高 3 位: 线程池状态
// ctl 的低 29 位: 线程数
private static final int COUNT_BITS = Integer.SIZE - 3;  // 29
private static final int CAPACITY = (1 << COUNT_BITS) - 1;  // 2^29 - 1 ≈ 5亿

// 状态 (高3位)
private static final int RUNNING    = -1 << COUNT_BITS;  // 111
private static final int SHUTDOWN   =  0 << COUNT_BITS;  // 000
private static final int STOP       =  1 << COUNT_BITS;  // 001
private static final int TIDYING    =  2 << COUNT_BITS;  // 010
private static final int TERMINATED =  3 << COUNT_BITS;  // 011

private static int runStateOf(int c)     { return c & ~CAPACITY; }  // 取高3位
private static int workerCountOf(int c)  { return c & CAPACITY; }   // 取低29位
private static int ctlOf(int rs, int wc) { return rs | wc; }        // 组合
makefile 复制代码
ctl 字段布局 (32位):
┌─────┬───────────────────────────────────────┐
│ 3位 │              29位                      │
│状态 │              线程数                    │
└─────┴───────────────────────────────────────┘

RUNNING:    111  0000000...0  (-1)
SHUTDOWN:   000  0000000...0  (0)
STOP:       001  0000000...0
TIDYING:    010  0000000...0
TERMINATED: 011  0000000...0

线程池状态流转

scss 复制代码
┌──────────────────────────────────────────────────────────┐
│                                                          │
│  RUNNING ──shutdown()──▶ SHUTDOWN ──全部完成──▶ TIDYING  │
│     │                       │                    │       │
│     │                       │              terminated() │
│     │                       │                    │       │
│     └──shutdownNow()──▶ STOP ──全部完成──▶ TIDYING──▶ TERMINATED
│                                                          │
│  RUNNING: 接收新任务,处理队列任务                       │
│  SHUTDOWN: 不接收新任务,处理队列任务                    │
│  STOP: 不接收新任务,不处理队列,中断执行中任务          │
│  TIDYING: 所有任务终止,workerCount=0,即将terminated    │
│  TERMINATED: terminated() 执行完毕                       │
│                                                          │
└──────────────────────────────────────────────────────────┘

五、Worker:线程与任务的封装

java 复制代码
private final class Worker extends AbstractQueuedSynchronizer
        implements Runnable {

    final Thread thread;     // Worker 对应的线程
    Runnable firstTask;      // 第一个任务(可能为 null)
    volatile long completedTasks;  // 完成任务数

    Worker(Runnable firstTask) {
        setState(-1);  // 禁止中断直到 runWorker
        this.firstTask = firstTask;
        this.thread = getThreadFactory().newThread(this);
    }

    public void run() {
        runWorker(this);
    }
}

runWorker:线程的工作循环

java 复制代码
final void runWorker(Worker w) {
    Thread wt = Thread.currentThread();
    Runnable task = w.firstTask;
    w.firstTask = null;
    w.unlock();  // 允许中断(state 从 -1 恢复到 0)

    boolean completedAbruptly = true;
    try {
        // 循环获取任务执行
        while (task != null || (task = getTask()) != null) {
            w.lock();  // Worker 加锁(区分是否在执行任务)
            // 线程池状态检查
            if ((runStateAtLeast(ctl.get(), STOP) ||
                 (Thread.interrupted() &&
                  runStateAtLeast(ctl.get(), STOP))) &&
                !wt.isInterrupted())
                wt.interrupt();
            try {
                beforeExecute(wt, task);  // 钩子方法
                Throwable thrown = null;
                try {
                    task.run();           // 执行任务
                } catch (RuntimeException x) {
                    thrown = x; throw x;
                } finally {
                    afterExecute(task, thrown);  // 钩子方法
                }
            } finally {
                task = null;
                w.completedTasks++;
                w.unlock();
            }
        }
        completedAbruptly = false;
    } finally {
        processWorkerExit(w, completedAbruptly);  // 线程退出处理
    }
}

getTask:从队列获取任务

java 复制代码
private Runnable getTask() {
    boolean timedOut = false;

    for (;;) {
        int c = ctl.get();
        int rs = runStateOf(c);

        // 线程池关闭且队列为空,返回 null(线程退出)
        if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
            decrementWorkerCount();
            return null;
        }

        int wc = workerCountOf(c);
        // 是否需要超时控制:
        // 1. allowCoreThreadTimeOut=true → 所有线程都超时
        // 2. 当前线程数 > corePoolSize → 非核心线程超时
        boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;

        // 线程数超过 max 且允许超时 → 返回 null 退出
        if ((wc > maximumPoolSize || (timed && timedOut))
            && (wc > 1 || workQueue.isEmpty())) {
            if (compareAndDecrementWorkerCount(c))
                return null;
            continue;
        }

        try {
            Runnable r;
            if (timed)
                r = workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS);  // 超时获取
            else
                r = workQueue.take();  // 阻塞获取(核心线程常驻)
            if (r != null)
                return r;
            timedOut = true;
        } catch (InterruptedException retry) {
            timedOut = false;
        }
    }
}

线程存活与回收逻辑

ini 复制代码
┌─────────────────────────────────────────────────────────┐
│  Worker 线程循环:                                       │
│                                                         │
│  while (true) {                                         │
│      task = getTask();                                  │
│      if (task == null) break;  // 超时或关闭 → 退出     │
│      task.run();                                        │
│  }                                                     │
│                                                         │
│  getTask 行为:                                          │
│  ├── 核心线程 (wc <= core): workQueue.take()            │
│  │   → 队列空则永久阻塞,线程常驻                      │
│  │                                                     │
│  ├── 非核心线程 (wc > core): workQueue.poll(keepAlive)  │
│  │   → 超时未获取 → 返回 null → 线程退出              │
│  │                                                     │
│  └── allowCoreThreadTimeOut=true:                      │
│      → 核心线程也走 poll 超时逻辑                      │
│                                                         │
│  线程退出 → processWorkerExit() → 从 workers 移除      │
│  → 如果线程不够 (wc-1 < core),补充新线程              │
└─────────────────────────────────────────────────────────┘

六、拒绝策略

java 复制代码
public interface RejectedExecutionHandler {
    void rejectedExecution(Runnable r, ThreadPoolExecutor executor);
}

四种内置策略

java 复制代码
// 1. AbortPolicy:抛异常(默认)
public static class AbortPolicy implements RejectedExecutionHandler {
    public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
        throw new RejectedExecutionException(
            "Task " + r.toString() + " rejected from " + e.toString());
    }
}

// 2. CallerRunsPolicy:由提交线程执行
public static class CallerRunsPolicy implements RejectedExecutionHandler {
    public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
        if (!e.isShutdown())
            r.run();  // 谁提交谁执行,形成背压
    }
}

// 3. DiscardPolicy:直接丢弃
public static class DiscardPolicy implements RejectedExecutionHandler {
    public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
        // 什么都不做,任务被静默丢弃
    }
}

// 4. DiscardOldestPolicy:丢弃队列最老的任务
public static class DiscardOldestPolicy implements RejectedExecutionHandler {
    public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
        if (!e.isShutdown()) {
            e.getQueue().poll();  // 丢弃队首
            e.execute(r);          // 重新提交
        }
    }
}
策略 行为 适用场景
AbortPolicy 抛 RejectedExecutionException 默认,需要感知拒绝
CallerRunsPolicy 提交线程自己执行 需要背压、不能丢失
DiscardPolicy 静默丢弃 允许丢失、不关心
DiscardOldestPolicy 丢弃最老任务 新任务更重要

自定义拒绝策略

java 复制代码
public class LogAndStorePolicy implements RejectedExecutionHandler {
    private final BlockingQueue<Runnable> backupQueue;

    public LogAndStorePolicy(BlockingQueue<Runnable> backupQueue) {
        this.backupQueue = backupQueue;
    }

    @Override
    public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
        // 1. 记录日志
        System.out.println("任务被拒绝: " + r + ", 池状态: " + executor);
        // 2. 存入备份队列,异步重试
        if (!backupQueue.offer(r)) {
            throw new RejectedExecutionException("备份队列也满了");
        }
    }
}

七、Executors 的四大陷阱

1. newFixedThreadPool

java 复制代码
public static ExecutorService newFixedThreadPool(int nThreads) {
    return new ThreadPoolExecutor(nThreads, nThreads,
                                  0L, TimeUnit.MILLISECONDS,
                                  new LinkedBlockingQueue<Runnable>());
    // LinkedBlockingQueue 无界!Integer.MAX_VALUE
}

陷阱:队列无界,任务堆积导致 OOM。

2. newSingleThreadExecutor

java 复制代码
public static ExecutorService newSingleThreadExecutor() {
    return new ThreadPoolExecutor(1, 1,
                                  0L, TimeUnit.MILLISECONDS,
                                  new LinkedBlockingQueue<Runnable>());
    // 同样无界队列
}

陷阱:同上。

3. newCachedThreadPool

java 复制代码
public static ExecutorService newCachedThreadPool() {
    return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                  60L, TimeUnit.SECONDS,
                                  new SynchronousQueue<Runnable>());
    // maximumPoolSize = Integer.MAX_VALUE!
}

陷阱:最大线程数无上限,大量请求时创建过多线程导致 OOM。

4. newScheduledThreadPool

java 复制代码
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
    return new ScheduledThreadPoolExecutor(corePoolSize);
}
// ScheduledThreadPoolExecutor 内部用 DelayQueue (无界)

陷阱:队列无界,任务堆积 OOM。

正确创建方式

java 复制代码
ThreadPoolExecutor pool = new ThreadPoolExecutor(
    10,                                              // corePoolSize
    20,                                              // maximumPoolSize
    60L, TimeUnit.SECONDS,                           // keepAliveTime
    new ArrayBlockingQueue<>(1000),                  // 有界队列!
    new ThreadFactory() {                            // 自定义线程工厂
        private final AtomicInteger counter = new AtomicInteger(0);
        @Override
        public Thread newThread(Runnable r) {
            Thread t = new Thread(r, "biz-pool-" + counter.getAndIncrement());
            t.setDaemon(false);
            return t;
        }
    },
    new ThreadPoolExecutor.CallerRunsPolicy()        // 降级策略
);

八、线程数怎么设置?

公式法

makefile 复制代码
最佳线程数 = 线程数 × (1 + 等待时间/计算时间)

CPU密集型:  N + 1  (等待时间≈0)
IO密集型:   2N 或更多 (等待时间 > 计算时间)
混合型:     拆分为CPU密集和IO密集两个线程池

实际调优

java 复制代码
// 动态调整参数(运行时可修改)
pool.setCorePoolSize(newCore);
pool.setMaximumPoolSize(newMax);

// 监控关键指标
System.out.println("活跃线程: " + pool.getActiveCount());
System.out.println("队列大小: " + pool.getQueue().size());
System.out.println("已完成: " + pool.getCompletedTaskCount());
System.out.println("最大并发: " + pool.getLargestPoolSize());

九、对比:execute vs submit

维度 execute submit
参数 Runnable Runnable/Callable
返回值 void Future
异常 直接抛出 封装在 Future 中
适用 无返回值任务 需要返回值/异常处理
java 复制代码
// execute: 异常直接抛出
pool.execute(() -> { throw new RuntimeException("boom"); });
// 异常在 worker 线程抛出,默认打印到 stderr

// submit: 异常封装在 Future 中
Future<?> future = pool.submit(() -> { throw new RuntimeException("boom"); });
try {
    future.get();  // 调用 get 时抛出 ExecutionException
} catch (ExecutionException e) {
    System.out.println("捕获: " + e.getCause());
}

十、面试高频问题速答

Q1: 为什么阿里规约禁止用 Executors?

newFixedThreadPoolnewSingleThreadExecutor 用无界队列(Integer.MAX_VALUE),任务堆积 OOM;newCachedThreadPool 最大线程数 Integer.MAX_VALUE,瞬间创建大量线程 OOM。应当用 ThreadPoolExecutor 显式设定有界队列和合理的 maximumPoolSize。

Q2: 核心线程能被回收吗?

默认不能。但当 allowCoreThreadTimeOut=true 时,核心线程在 keepAliveTime 内无任务也会退出。设置方式:pool.allowCoreThreadTimeOut(true)

Q3: 任务先入队列还是先创建非核心线程?

先入队列。execute 的逻辑是:核心线程满→入队列→队列满→非核心线程→拒绝。这导致如果用 LinkedBlockingQueue(无界),队列永远不满,非核心线程永远不会创建,maximumPoolSize 形同虚设。

Q4: 线程池中线程异常了会怎样?

execute 提交的任务:异常直接抛出,该线程销毁并创建新线程替代。submit 提交的任务:异常封装在 Future 中,get() 时抛出 ExecutionException,线程不销毁继续执行后续任务。

Q5: shutdown 和 shutdownNow 的区别?

shutdown:设为 SHUTDOWN 状态,不接受新任务,但处理完队列中的任务。shutdownNow:设为 STOP 状态,不接受新任务,丢弃队列任务,中断执行中的任务,返回未执行的任务列表。

Q6: workQueue 用什么队列好?

取决于场景:有界队列(ArrayBlockingQueue)推荐------防止 OOM;SynchronousQueue 适合直接传递(CachedThreadPool 用法);PriorityBlockingQueue 适合优先级任务。绝不推荐无界队列。

十一、总结

scss 复制代码
线程池核心链路:
┌──────────────────────────────────────────────────────┐
│                                                      │
│  execute(task):                                      │
│    1. wc < core? → addWorker(core) 创建核心线程      │
│    2. 队列未满? → workQueue.offer(task) 入队         │
│    3. wc < max? → addWorker(non-core) 非核心线程    │
│    4. reject(task) → 拒绝策略                       │
│                                                      │
│  Worker.run():                                       │
│    while (task = getTask()) {                        │
│        task.run();  ← 执行任务                      │
│    }                                                 │
│                                                      │
│  getTask():                                          │
│    核心线程 → take() 永久阻塞                       │
│    非核心 → poll(keepAlive) 超时退出                │
│                                                      │
│  正确创建:                                           │
│    ThreadPoolExecutor + 有界队列 + 自定义工厂       │
│    绝不用 Executors (无界队列/无限线程 → OOM)       │
│                                                      │
│  拒绝策略:                                           │
│    Abort(抛异常) / CallerRuns(背压)                 │
│    Discard(丢弃) / DiscardOldest(丢最老)            │
│                                                      │
└──────────────────────────────────────────────────────┘

线程池是 Java 并发的核心组件,理解了 execute 的四步流转、Worker 的工作循环、getTask 的存活逻辑和拒绝策略的触发时机,面试中任何线程池问题都能从源码层面回答。最重要的一条工程经验:永远不要用 Executors,永远用有界队列

相关推荐
cpolar技术支持1 小时前
Kafka Streams 窗口统计怎么验收:本地跑订单流聚合,用 cpolar 给同事看只读结果页
java·docker·kafka·cpolar·kafka streams
爱读源码的大都督1 小时前
DeepSeek面试官问:生产RAG系统回答不准确,该如何定位和优化?这样回答,能让面试官当场给你Offer!
java·后端·python
杨运交2 小时前
[069][公共模块]Spring Boot 全局异常处理与参数校验实战(下):校验异常精细化处理与 WebFlux 适配
java·spring boot·后端
Raas1002 小时前
MAI Gateway(魔芋企业级AI网关)对比分析:AI网关和OpenRouter区别?企业级能力差距一览
java·服务器·网络·人工智能·gateway·ai网关·mai gateway
APItesterCris2 小时前
告别人工盯品!借助 Open‑Claw 快速搭建电商商品全自动监控与数据分析系统(完整实操代码)
java·大数据·前端·数据库
青山木3 小时前
Hot 100 --- 跳跃游戏 II
java·数据结构·算法·leetcode·贪心算法
ly76893 小时前
Spring 中的 @Configuration 与 @Component 差异:为何代理时机决定 Bean 生命周期行为
java·后端·spring·注解·代理·bean生命周期
qq_452396233 小时前
第十二篇:《数据采集:Grafana Alloy、Fluent Bit、Vector 的选型与配置》
java·贪心算法·grafana
珍珠先生3 小时前
P16 · IDEA 启动报错:找不到或无法加载主类
java