Java 线程池(ThreadPoolExecutor)源码分析

Java 线程池(ThreadPoolExecutor)源码分析

一、整体结构

scss 复制代码
ThreadPoolExecutor
├── 核心参数:corePoolSize、maximumPoolSize、workQueue、keepAliveTime、threadFactory、handler
├── 状态:ctl (AtomicInteger,高3位状态 + 低29位线程数)
├── 工作线程:Worker(继承 AQS,实现 Runnable)
└── 任务队列:BlockingQueue<Runnable>

二、execute():提交任务入口

java 复制代码
public void execute(Runnable command) {
    if (command == null)
        throw new NullPointerException();
    
    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:队列满,尝试创建非核心线程
    if (!addWorker(command, false))
        reject(command);  // 步骤4:创建失败,执行拒绝策略
}

步骤说明

步骤 逻辑 说明 原因
1 workerCountOf(c) < corePoolSize 当前工作线程数小于核心线程数 核心线程是常驻的,优先用核心线程执行,减少线程创建开销
1.1 addWorker(command, true) 创建核心线程并执行该任务 核心线程不足时立即创建,firstTask 直接交给新线程执行,无需入队
2 workQueue.offer(command) 线程数已满,尝试把任务放入队列 核心线程已满时先入队等待,队列有界时可控制背压,避免无限制创建线程
2.1 isRunning(recheck) 再次检查池是否还在运行,防止并发下状态变化 入队与取任务存在并发,可能在此期间调用了 shutdown,需 double-check 避免向已关闭池提交
2.2 workerCountOf(recheck) == 0 若没有工作线程,补一个空任务 Worker,保证队列任务能被消费 可能所有 Worker 已退出,但队列还有任务,需补 Worker 否则任务永远无法执行
3 addWorker(command, false) 队列已满,尝试创建非核心线程执行任务 队列满说明负载高,临时扩容到 maximumPoolSize 以应对峰值
4 reject(command) 线程数已达 maximumPoolSize,执行拒绝策略 池已满,按 RejectedExecutionHandler 策略处理(抛异常、丢弃、调用者执行等)

三、addWorker():添加工作线程

java 复制代码
private boolean addWorker(Runnable firstTask, boolean core) {
    retry:
    for (;;) {
        int c = ctl.get();
        int rs = runStateOf(c);
        // 步骤1:检查线程池状态
        if (rs >= SHUTDOWN && !(rs == SHUTDOWN && firstTask == null && !workQueue.isEmpty()))
            return false;
        for (;;) {
            int wc = workerCountOf(c);
            // 步骤2:检查线程数是否超限
            if (wc >= CAPACITY || wc >= (core ? corePoolSize : maximumPoolSize))
                return false;
            if (compareAndIncrementWorkerCount(c))
                break retry;
            c = ctl.get();
            if (runStateOf(c) != rs)
                continue retry;
        }
    }
    // 步骤3:创建 Worker 并加入 workers 集合
    boolean workerStarted = false;
    boolean workerAdded = false;
    Worker w = null;
    try {
        w = new Worker(firstTask);
        final Thread t = w.thread;
        if (t != null) {
            final ReentrantLock mainLock = this.mainLock;
            mainLock.lock();
            try {
                int rs = runStateOf(ctl.get());
                if (rs < SHUTDOWN || (rs == SHUTDOWN && firstTask == null)) {
                    if (t.isAlive())
                        throw new IllegalThreadStateException();
                    workers.add(w);
                    workerAdded = true;
                }
            } finally {
                mainLock.unlock();
            }
            if (workerAdded) {
                t.start();  // 步骤4:启动线程
                workerStarted = true;
            }
        }
    } finally {
        if (! workerStarted)
            addWorkerFailed(w);
    }
    return workerStarted;
}

步骤说明

步骤 逻辑 说明 原因
1 rs >= SHUTDOWN 判断 若已 SHUTDOWN/STOP/TIDYING/TERMINATED,一般不再接受新任务 关闭后不应再创建新 Worker,避免资源浪费和状态混乱
1.1 特殊:SHUTDOWN && firstTask==null && !workQueue.isEmpty() 允许添加「空任务」Worker 去消费队列剩余任务 shutdown 后不再接新任务,但需把队列中已有任务执行完,空 Worker 会通过 getTask 取队列任务
2 wc >= corePoolSizemaximumPoolSize 核心/非核心线程数已达上限,不再创建 防止超过配置的线程数,保证资源可控
2.1 compareAndIncrementWorkerCount(c) CAS 增加工作线程计数,避免并发超限 多线程并发 execute 时,CAS 保证 workerCount 准确,防止超卖
3 new Worker(firstTask)workers.add(w) 创建 Worker,加入 workers 集合 Worker 封装线程与任务,workers 用于 shutdown 时遍历中断
4 t.start() 启动 Worker 内部线程,进入 runWorker() 线程启动后进入循环取任务、执行任务的流程

四、Worker:工作线程封装

java 复制代码
private final class Worker extends AbstractQueuedSynchronizer implements Runnable {
    final Thread thread;      // 实际执行任务的线程
    Runnable firstTask;       // 创建时分配的第一个任务(可为 null)
    
    Worker(Runnable firstTask) {
        setState(-1);  // 初始状态,防止被中断
        this.firstTask = firstTask;
        this.thread = getThreadFactory().newThread(this);  // 把自己作为 Runnable
    }
    
    public void run() {
        runWorker(this);  // 委托给 runWorker
    }
}

步骤说明

步骤 逻辑 说明 原因
1 setState(-1) 初始 AQS 状态为 -1 防止 Worker 刚创建时被误判为可中断,runWorker 中 unlock 后才允许中断
2 getThreadFactory().newThread(this) 用线程工厂创建线程,this 作为 Runnable 统一线程创建方式,Worker 的 run 会调用 runWorker,形成执行入口
3 继承 AQS 实现简单锁 区分空闲(lock 前)与工作中(lock 后),shutdown 时只中断空闲 Worker,避免打断正在执行的任务

五、runWorker():工作线程主循环

java 复制代码
final void runWorker(Worker w) {
    Thread wt = Thread.currentThread();
    Runnable task = w.firstTask;
    w.firstTask = null;
    w.unlock();  // 允许中断
    boolean completedAbruptly = true;
    try {
        // 步骤1:循环取任务(firstTask 或 getTask())
        while (task != null || (task = getTask()) != null) {
            w.lock();
            // 步骤2:检查是否需要中断(shutdown 等)
            if ((runStateAtLeast(ctl.get(), STOP) || 
                 (Thread.interrupted() && runStateAtLeast(ctl.get(), STOP))) &&
                !wt.isInterrupted())
                wt.interrupt();
            try {
                beforeExecute(wt, task);  // 钩子
                try {
                    task.run();  // 步骤3:执行任务
                } finally {
                    afterExecute(task, thrown);  // 钩子
                }
            } finally {
                task = null;
                w.completedTasks++;
                w.unlock();
            }
        }
        completedAbruptly = false;
    } finally {
        processWorkerExit(w, completedAbruptly);  // 步骤4:Worker 退出处理
    }
}

步骤说明

步骤 逻辑 说明 原因
1 task = w.firstTaskgetTask() 优先执行创建时传入的 firstTask,之后从队列取任务 firstTask 是 addWorker 时传入的,直接执行可少一次入队出队;之后从队列取任务实现复用
2 状态检查 + 中断 若池已 STOP 或当前线程被中断,确保线程被中断,以便快速退出 STOP 时要尽快结束,中断可让阻塞在 getTask 的线程及时退出
3 task.run() 直接执行任务(不是 start(),复用当前线程) Worker 线程就是工作线程,直接 run 在当前线程执行,无需再起新线程
4 processWorkerExit() 线程退出时:减少 worker 计数、必要时补充新 Worker、尝试终止线程池 保证 workerCount 正确,SHUTDOWN 时若队列非空可补 Worker,最终尝试转为 TERMINATED

六、getTask():从队列取任务

java 复制代码
private Runnable getTask() {
    boolean timedOut = false;
    for (;;) {
        int c = ctl.get();
        int rs = runStateOf(c);
        // 步骤1:池已关闭且队列空,直接返回 null
        if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
            decrementWorkerCount(c);
            return null;
        }
        int wc = workerCountOf(c);
        // 步骤2:是否允许超时回收(非核心 或 允许核心超时)
        boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
        if ((wc > maximumPoolSize || (timed && timedOut))
            && (wc > 1 || workQueue.isEmpty())) {
            if (compareAndDecrementWorkerCount(c))
                return null;
            continue;
        }
        try {
            // 步骤3:阻塞取任务(带超时或不带超时)
            Runnable r = timed ?
                workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
                workQueue.take();
            if (r != null)
                return r;
            timedOut = true;
        } catch (InterruptedException retry) {
            timedOut = false;
        }
    }
}

步骤说明

步骤 逻辑 说明 原因
1 `rs >= SHUTDOWN && (rs >= STOP workQueue.isEmpty())`
2 `timed = allowCoreThreadTimeOut wc > corePoolSize`
2.1 wc > maximumPoolSize(timed && timedOut) 线程数超限或上次取任务超时,且满足回收条件时,减少 worker 并返回 null 扩容后负载下降需缩容;超时取到 null 表示空闲过久,可回收以释放资源
3 poll(keepAliveTime) / take() 超时取或阻塞取,超时返回 null 会触发 timedOut = true,下次循环可能回收线程 核心线程用 take 永久阻塞保持存活;非核心用 poll 超时,超时后 timedOut 为 true 可能触发回收

七、整体执行流程

scss 复制代码
execute(task)
    │
    ├─ 线程数 < corePoolSize ──→ addWorker(task, true) ──→ 新建核心线程执行
    │
    ├─ 线程数 ≥ corePoolSize
    │       │
    │       ├─ workQueue.offer(task) 成功 ──→ 任务入队
    │       │       │
    │       │       └─ workerCount == 0 ──→ addWorker(null, false) 补一个空 Worker
    │       │
    │       └─ 队列满 ──→ addWorker(task, false) ──→ 新建非核心线程执行
    │               │
    │               └─ 失败 ──→ reject(task)
    │
    └─ Worker 启动 ──→ runWorker()
            │
            ├─ 执行 firstTask
            │
            └─ 循环 getTask()
                    │
                    ├─ take() / poll() 取到任务 ──→ task.run()
                    │
                    └─ 返回 null(超时或池关闭)──→ processWorkerExit()

八、状态与 ctl

java 复制代码
// ctl = 高3位状态 + 低29位线程数
RUNNING    = -1 << 29   // 接受新任务、处理队列
SHUTDOWN   =  0 << 29   // 不再接受新任务,继续处理队列
STOP       =  1 << 29   // 不再接受新任务,不处理队列,中断正在执行的任务
TIDYING    =  2 << 29   // 所有任务已结束,workerCount=0
TERMINATED =  3 << 29   // terminated() 已执行

九、小结

环节 作用
execute() 决定:新建核心线程 / 入队 / 新建非核心线程 / 拒绝
addWorker() 创建 Worker、加入 workers、启动线程
Worker 封装线程和 firstTask,实现 Runnable
runWorker() 循环取任务并执行,退出时调用 processWorkerExit
getTask() 从队列取任务,控制超时回收和池关闭时的退出
相关推荐
代码搬运媛2 小时前
Jest 测试框架详解与实现指南
前端
counterxing3 小时前
我把 Codex 里的 Skills 做成了一个 MCP,还支持分享
前端·agent·ai编程
wangqiaowq3 小时前
windows下nginx的安装
linux·服务器·前端
之歆3 小时前
DAY_12JavaScript DOM 完全指南(二):实战与性能篇
开发语言·前端·javascript·ecmascript
发现一只大呆瓜4 小时前
Vite凭什么这么快?3分钟带你彻底搞懂 Vite 热更新的幕后黑手
前端·面试·vite
Maimai108084 小时前
React如何用 @microsoft/fetch-event-source 落地 SSE:比原生 EventSource 更灵活的实时推送方案
前端·javascript·react.js·microsoft·前端框架·reactjs·webassembly
kyriewen5 小时前
产品经理把PRD写成“天书”,我用AI半小时重写了一遍,他当场愣住
前端·ai编程·cursor
humcomm6 小时前
元框架的工作原理详解
前端·前端框架
canonical_entropy6 小时前
Attractor Before Harness: AI 大规模开发的方法论
前端·aigc·ai编程
zhangxingchao7 小时前
多 Agent 架构到底怎么选?从 Claude Agent Teams、Cognition/Devin 到工程落地原则
前端·人工智能·后端