ThreadPoolExecutor线程回收流程详解

ThreadPoolExecutor线程回收流程详解

核心参数影响回收

1. 关键参数

java 复制代码
// 影响回收的三大参数
corePoolSize:      // 核心线程数 - 默认不回收
maximumPoolSize:   // 最大线程数
keepAliveTime:     // 空闲线程存活时间
TimeUnit:          // 时间单位
allowCoreThreadTimeOut: // 是否允许核心线程超时回收

2. 线程回收流程

流程概览图

ini 复制代码
┌─────────────────────────────────────────────────────┐
│  线程执行任务完成                                    │
├─────────────────────────────────────────────────────┤
│  判断是否为核心线程?                                │
│    ↓ 是                     ↓ 否                    │
│  ┌─────────────────┐   ┌──────────────────────┐    │
│  │ 核心线程不回收    │   │ 从队列获取新任务      │    │
│  │ (除非设置了      │   │  ↓                   │    │
│  │  allowCoreThread│   │ 等待keepAliveTime    │    │
│  │  TimeOut=true)  │   │  ↓                   │    │
│  └─────────────────┘   │ 超时未获取到任务?    │    │
│                        │   ↓ 是                │    │
│                        │ 线程被回收            │    │
│                        └──────────────────────┘    │
└─────────────────────────────────────────────────────┘

3. 详细回收机制

3.1 Worker(工作线程)的生命周期

java 复制代码
// ThreadPoolExecutor中的Worker内部类
private final class Worker extends AbstractQueuedSynchronizer implements Runnable {
    final Thread thread;    // 实际执行的线程
    Runnable firstTask;     // 初始任务
    
    Worker(Runnable firstTask) {
        this.firstTask = firstTask;
        this.thread = getThreadFactory().newThread(this);
    }
    
    public void run() {
        runWorker(this);  // 核心执行逻辑
    }
}

3.2 核心方法:runWorker()

java 复制代码
final void runWorker(Worker w) {
    Thread wt = Thread.currentThread();
    Runnable task = w.firstTask;
    w.firstTask = null;
    w.unlock(); // 允许中断
    boolean completedAbruptly = true;
    
    try {
        // 循环获取任务并执行
        while (task != null || (task = getTask()) != null) {
            w.lock();
            // 检查线程是否应该被中断
            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;
                } catch (Error x) {
                    thrown = x; throw x;
                } catch (Throwable x) {
                    thrown = x; throw new Error(x);
                } finally {
                    afterExecute(task, thrown);  // 钩子方法
                }
            } finally {
                task = null;
                w.completedTasks++;
                w.unlock();
            }
        }
        completedAbruptly = false;
    } finally {
        // 工作线程退出处理
        processWorkerExit(w, completedAbruptly);
    }
}

3.3 关键方法:getTask() - 决定线程是否回收

java 复制代码
private Runnable getTask() {
    boolean timedOut = false; // 上次poll是否超时
    
    for (;;) {
        int c = ctl.get();
        int rs = runStateOf(c);
        
        // 检查线程池状态
        if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
            decrementWorkerCount();
            return null;
        }
        
        int wc = workerCountOf(c);
        
        // 判断是否需要超时控制
        boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
        
        // 检查是否需要减少线程数
        if ((wc > maximumPoolSize || (timed && timedOut))
            && (wc > 1 || workQueue.isEmpty())) {
            if (compareAndDecrementWorkerCount(c))
                return null;
            continue;
        }
        
        try {
            // 关键:根据timed决定获取任务的方式
            Runnable r = timed ?
                workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
                workQueue.take();  // 核心线程阻塞等待
            
            if (r != null)
                return r;
            timedOut = true;  // 标记超时
        } catch (InterruptedException retry) {
            timedOut = false;
        }
    }
}

3.4 线程退出:processWorkerExit()

java 复制代码
private void processWorkerExit(Worker w, boolean completedAbruptly) {
    if (completedAbruptly) // 异常退出
        decrementWorkerCount();
    
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        completedTaskCount += w.completedTasks;
        workers.remove(w);  // 从workers集合中移除
    } finally {
        mainLock.unlock();
    }
    
    // 尝试终止线程池
    tryTerminate();
    
    int c = ctl.get();
    if (runStateLessThan(c, STOP)) {
        if (!completedAbruptly) {
            // 正常退出的线程处理
            int min = allowCoreThreadTimeOut ? 0 : corePoolSize;
            if (min == 0 && !workQueue.isEmpty())
                min = 1;
            if (workerCountOf(c) >= min)
                return;
        }
        // 补充新的Worker(维持最小线程数)
        addWorker(null, false);
    }
}

4. 回收场景分析

场景1:非核心线程空闲超时

java 复制代码
// 假设配置:
// corePoolSize=5, maximumPoolSize=10, keepAliveTime=60s, allowCoreThreadTimeOut=false

// 执行过程:
// 1. 当前有8个线程(5核心+3非核心)
// 2. 任务减少,3个非核心线程空闲
// 3. 每个非核心线程调用 workQueue.poll(60, SECONDS)
// 4. 60秒内未获取到任务 → poll返回null → getTask()返回null
// 5. runWorker()循环结束 → processWorkerExit()移除Worker
// 6. 线程数降回5个(核心线程)

场景2:核心线程超时回收(需要设置)

java 复制代码
// 开启核心线程超时
executor.allowCoreThreadTimeOut(true);

// 执行过程:
// 1. 所有线程都会调用 timed ? poll() : take()
// 2. 当allowCoreThreadTimeOut=true时,timed永远为true
// 3. 所有线程都会调用 workQueue.poll(keepAliveTime, unit)
// 4. 超时后所有线程都可能被回收,直到线程数为0

场景3:线程池关闭时

java 复制代码
// shutdown()时:
// 1. 空闲线程会从getTask()中返回null(检查到SHUTDOWN状态)
// 2. 正在执行任务的线程会继续执行完当前任务
// 3. 所有线程最终都会被回收

// shutdownNow()时:
// 1. 所有线程都会被中断(包括正在执行任务的)
// 2. getTask()会立即返回null
// 3. 所有线程快速退出

5. 源码中线程数的维护

线程计数(ctl字段)

java 复制代码
// ctl是一个AtomicInteger,包含两部分信息:
// 高3位:线程池状态(RUNNING, SHUTDOWN, STOP, TIDYING, TERMINATED)
// 低29位:工作线程数量

private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));

// 线程数操作方法
private boolean compareAndDecrementWorkerCount(int expect) {
    return ctl.compareAndSet(expect, expect - 1);
}

private void decrementWorkerCount() {
    do {} while (!compareAndDecrementWorkerCount(ctl.get()));
}

6. 实际案例演示

示例:观察线程回收

java 复制代码
public class ThreadPoolRecycleDemo {
    public static void main(String[] args) throws Exception {
        ThreadPoolExecutor executor = new ThreadPoolExecutor(
            2,  // corePoolSize
            5,  // maximumPoolSize
            3,  // keepAliveTime
            TimeUnit.SECONDS,
            new LinkedBlockingQueue<>(10)
        );
        
        // 提交10个任务
        for (int i = 0; i < 10; i++) {
            final int taskId = i;
            executor.execute(() -> {
                System.out.println(Thread.currentThread().getName() + "执行任务" + taskId);
                try {
                    Thread.sleep(1000);  // 模拟任务执行
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            });
        }
        
        // 监控线程数变化
        new Thread(() -> {
            while (true) {
                System.out.println("当前线程数: " + executor.getPoolSize() + 
                                 ", 活动线程数: " + executor.getActiveCount());
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    break;
                }
            }
        }).start();
        
        Thread.sleep(10000);  // 等待10秒
        
        // 观察:非核心线程空闲3秒后是否被回收
        System.out.println("10秒后线程数: " + executor.getPoolSize());
        
        executor.shutdown();
    }
}

7. 注意事项和最佳实践

注意事项:

  1. 核心线程默认不回收 :除非设置 allowCoreThreadTimeOut(true)
  2. 回收是异步的:不会立即回收,需要等待空闲超时
  3. 线程中断shutdownNow()会中断所有线程,可能影响任务执行
  4. 线程复用:线程执行完一个任务后不会销毁,而是继续获取新任务

最佳实践:

java 复制代码
// 1. 根据业务场景合理设置参数
ThreadPoolExecutor executor = new ThreadPoolExecutor(
    Runtime.getRuntime().availableProcessors(),  // CPU密集型
    Runtime.getRuntime().availableProcessors() * 2,  // IO密集型可设更大
    60L, TimeUnit.SECONDS,
    new LinkedBlockingQueue<>(1000)
);

// 2. 监控线程池状态
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
executor.setThreadFactory(new CustomThreadFactory());

// 3. 优雅关闭
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
    executor.shutdown();
    try {
        if (!executor.awaitTermination(60, TimeUnit.SECONDS)) {
            executor.shutdownNow();
        }
    } catch (InterruptedException e) {
        executor.shutdownNow();
    }
}));

线程回收流程图:

vbscript 复制代码
┌─────────────────────────────────────────────┐
│             任务执行完毕                     │
├─────────────────────────────────────────────┤
│  进入getTask()方法获取新任务                │
├─────────────────────────────────────────────┤
│  检查线程池状态                             │
│  ↓ 如果SHUTDOWN/STOP → 返回null            │
├─────────────────────────────────────────────┤
│  判断是否需要超时控制:                      │
│  timed = allowCoreThreadTimeOut ||          │
│         workerCount > corePoolSize          │
├─────────────────────────────────────────────┤
│  timed为true: 调用poll(keepAliveTime)      │
│  timed为false: 调用take()(阻塞等待)       │
├─────────────────────────────────────────────┤
│  poll超时返回null → 标记timedOut=true      │
├─────────────────────────────────────────────┤
│  检查是否应该减少线程数                     │
│  ↓ 如果满足条件 → CAS减少计数并返回null     │
├─────────────────────────────────────────────┤
│  getTask()返回null → 退出runWorker循环     │
├─────────────────────────────────────────────┤
│  执行processWorkerExit()                   │
│  ↓ 从workers集合移除Worker                 │
│  ↓ 减少线程计数                            │
│  ↓ 尝试终止线程池                          │
│  ↓ 必要时补充新Worker                      │
├─────────────────────────────────────────────┤
│  Worker线程结束运行 → 被JVM回收             │
└─────────────────────────────────────────────┘

这就是ThreadPoolExecutor线程回收的完整流程,核心在于getTask()方法如何根据配置决定线程是否应该继续等待任务还是被回收。

相关推荐
狂奔小菜鸡1 小时前
Day24 | Java泛型通配符与边界解析
java·后端·java ee
用户68545375977691 小时前
为什么你的Python代码那么乱?因为你不会用装饰器
后端
天天摸鱼的java工程师1 小时前
🐇RabbitMQ 从入门到业务实战:一个 Java 程序员的实战手记
java·后端
Frank_zhou1 小时前
CopyOnWriteArrayList
后端
楚兴1 小时前
使用 Eino 和 Ollama 构建智能 Go 应用:从简单问答到复杂 Agent
人工智能·后端
小镇cxy2 小时前
VibeCoding实践,Spec+Claude Code小程序开发
后端·claude·vibecoding
GeekPMAlex2 小时前
深入理解 Python 元组、哈希、堆与 enumerate
后端
踏浪无痕2 小时前
从单体PHP到微服务:一个五年老项目的血泪重构史
后端·面试·架构
shark_chili2 小时前
基于arthas量化监控诊断java应用方法论与实践
后端