深入分析线程池

核心知识点

1.1 ThreadPoolExecutor 七大参数

自行结合相关流程描述这7个参数

java 复制代码
public ThreadPoolExecutor(
    int corePoolSize,              // 1. 核心线程数
    int maximumPoolSize,           // 2. 最大线程数
    long keepAliveTime,            // 3. 空闲线程存活时间
    TimeUnit unit,                 // 4. 时间单位
    BlockingQueue<Runnable> workQueue,  // 5. 工作队列
    ThreadFactory threadFactory,   // 6. 线程工厂
    RejectedExecutionHandler handler     // 7. 拒绝策略
)
参数详解表
参数 说明 项目中的值 面试重点
corePoolSize 核心线程数,常驻线程池 10 即使空闲也不会被回收
maximumPoolSize 最大线程数 20 线程池能创建的最大线程数
keepAliveTime 空闲线程存活时间 60秒 非核心线程的空闲时间超过此值会被回收
unit 时间单位 TimeUnit.SECONDS 配合keepAliveTime使用
workQueue 工作队列 LinkedBlockingQueue(500) 存储待执行任务的阻塞队列
threadFactory 线程工厂 自定义ThreadFactory 用于创建线程,可自定义线程名、优先级等
handler 拒绝策略 CallerRunsPolicy 队列满且线程数达到最大值时的处理策略

1.2 为什么核心线程数不会被回收

text 复制代码
核心设计思想:
  1. 核心线程是线程池的"常驻军"
  2. 减少线程创建/销毁的开销
  3. 保证线程池的基本处理能力
  4. 即使空闲也保持一定数量,快速响应新任务
1.2.1. 核心机制:核心线程不会被回收的关键在 getTask() 方法。源码逻辑如下:
java 复制代码
private Runnable getTask() {
    // 记录上一次从队列获取任务是否超时
    // 只有使用poll()方法时才可能超时,take()方法不会超时
    boolean timedOut = false; 
    
    for (;;) { // 无限循环,直到获取到任务或线程被回收
        int c = ctl.get();  // 获取ctl值(包含线程池状态和线程数)
        int rs = runStateOf(c);  // 提取线程池运行状态
        
        /* 
         * 检查线程池状态,判断是否需要回收线程
         * 条件1:rs >= SHUTDOWN 线程池已关闭
         * 条件2:rs >= STOP(不处理队列任务) 或 workQueue.isEmpty(队列为空)
         * 满足条件说明线程池不需要再处理任务,可以回收线程
         */
        if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
            decrementWorkerCount();  // 减少worker计数
            return null;  // 返回null表示线程需要被回收
        }
        
        int wc = workerCountOf(c);  // 获取当前工作线程数
        
        /* 
         * 🔑 核心线程和非核心线程的关键区别判断
         * 
         * timed = true 表示当前线程"可能"会被回收(使用poll超时机制)
         * timed = false 表示当前线程"不会"被回收(使用take永久阻塞)
         * 
         * 条件1:allowCoreThreadTimeOut = true(允许核心线程超时,默认false)
         * 条件2:wc > corePoolSize(当前线程数超过核心线程数)
         * 
         * 核心线程:默认情况下timed=false,不会被回收
         * 非核心线程:timed=true,空闲超时后会被回收
         */
        boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
        
        /*
         * 🔑 线程回收的核心判断逻辑
         * 
         * 条件分解:
         * 1. wc > maximumPoolSize:当前线程数超过最大线程数(需要回收)
         * 2. (timed && timedOut):允许超时 且 上一次获取任务超时了(需要回收)
         * 
         * 3. (wc > 1 || workQueue.isEmpty()):保证至少保留1个线程 或 队列为空
         *    - 如果wc > 1:还有其他线程,当前线程可以回收
         *    - 如果workQueue.isEmpty():队列为空,可以回收线程
         * 
         * 核心线程回收路径:只有allowCoreThreadTimeOut=true时才可能满足条件2
         * 非核心线程回收路径:timed=true,当poll()超时后timedOut=true,满足条件2
         */
        if ((wc > maximumPoolSize || (timed && timedOut))
            && (wc > 1 || workQueue.isEmpty())) {
            
            // 通过CAS减少worker计数,确保线程安全
            if (compareAndDecrementWorkerCount(c))
                return null;  // 返回null,runWorker()循环结束,线程被回收
            continue;  // CAS失败,重试
        }
        
        try {
            /*
             * 🔑 核心线程和非核心线程的阻塞方式本质区别
             * 
             * timed = true(非核心线程):
             * - 使用poll(keepAliveTime, TimeUnit.NANOSECONDS)
             * - 带超时的阻塞,keepAliveTime后返回null
             * - 下次循环时timedOut=true,满足回收条件
             * 
             * timed = false(核心线程):
             * - 使用workQueue.take()
             * - 永久阻塞,直到队列中有任务
             * - 不会返回null,所以timedOut永远为false
             * - 永远不会满足回收条件,线程得以保留
             */
            Runnable r = timed ?
                workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :  // 非核心线程:可能超时返回null
                workQueue.take();  // 核心线程:永久阻塞,不会返回null
            
            if (r != null)
                return r;  // 获取到任务,返回给runWorker()执行
            
            /*
             * 只有poll()超时返回null时才会执行到这里
             * take()不会超时,所以不会执行到这里
             * 
             * 设置timedOut=true,下次循环时如果timed仍为true
             * 就会满足 (timed && timedOut) 的回收条件
             */
            timedOut = true;  
            
        } catch (InterruptedException retry) {
            // 线程被中断,重置timedOut标志
            // 如果是take()被中断,会重新循环继续take()
            // 如果是poll()被中断,会重新循环继续poll()
            timedOut = false;
        }
    }
}
1.2.2. 核心逻辑解析(其实代码中有注释,这里再次重复下,看懂了上述代码可跳过)
  • 关键变量 timed
java 复制代码
boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;

// allowCoreThreadTimeOut = false(默认)且 wc <= corePoolSize:timed = false → 核心线程
// allowCoreThreadTimeOut = true 或 wc > corePoolSize:timed = true → 非核心线程或允许超时的核心线程
  • 核心线程的阻塞方式
java 复制代码
Runnable r = timed ?
   	workQueue.poll(keepAliveTime, TimeUnit.NANANOSECONDS) :  // 非核心线程
    workQueue.take();  // 核心线程:永久阻塞

// 核心线程(timed = false):使用 workQueue.take(),永久阻塞等待任务,不会超时返回 null,因此不会被回收
// 非核心线程(timed = true):使用 workQueue.poll(keepAliveTime),超时返回 null,触发回收

// workQueue.take() 是一个阻塞方法 ,它是 BlockingQueue 接口的核心方法之一,专门用于实现 线程间的阻塞等待 。
// Runnable task = workQueue.take();  // 如果队列中没有任务,当前线程会阻塞在这里
// - 当前线程会 暂停执行 , 不会消耗 CPU
// - 直到有其他线程向队列中放入任务(如 execute() 提交任务)
// - 然后 take() 返回该任务,当前线程继续执行
  • 回收判断条件
java 复制代码
if ((wc > maximumPoolSize || (timed && timedOut))
    && (wc > 1 || workQueue.isEmpty())) {
    // 回收线程
    return null;
}

// 核心线程:timed = false,即使 timedOut = true,条件 (timed && timedOut) 仍为 false,不会被回收
// 非核心线程:timed = true 且 timedOut = true 时,满足条件,会被回收
1.2.3. 完整执行流程对比
text 复制代码
核心线程执行流程:
  runWorker()
    → getTask()
      → timed = false (因为 wc <= corePoolSize)
      → workQueue.take()  // 永久阻塞,等待任务
      → 获取到任务 → 返回任务 → 执行任务
      → 继续循环,再次调用 getTask()
      → 永远不会返回 null
      → 线程永远不会退出 ✅

非核心线程执行流程:
  runWorker()
    → getTask()
      → timed = true (因为 wc > corePoolSize)
      → workQueue.poll(keepAliveTime)  // 超时阻塞
      → 如果超时 → 返回 null
      → 线程退出循环
      → 线程被回收 ✅
1.2.4. 设计原因

a. 性能考虑

text 复制代码
线程创建/销毁的开销:
  1. 创建线程:
     - 分配栈空间(默认1MB)
     - 系统调用(创建内核线程)
     - 初始化线程上下文
     - 开销:约1-10ms
  
  2. 销毁线程:
     - 释放栈空间
     - 系统调用(销毁内核线程)
     - 清理线程上下文
     - 开销:约1-5ms

核心线程保持常驻:
  ✅ 避免频繁创建/销毁线程
  ✅ 减少系统调用开销
  ✅ 提高响应速度

b. 响应速度

text 复制代码
场景对比:

场景1:核心线程被回收
  任务到达 → 创建新线程(1-10ms)→ 执行任务
  总延迟:1-10ms + 任务执行时间

场景2:核心线程常驻
  任务到达 → 立即执行(0ms)→ 执行任务
  总延迟:任务执行时间

性能提升:1-10ms(对于短任务,提升明显)

c. 资源利用

text 复制代码
核心线程的资源占用:
  - 每个线程:约1MB栈空间
  - 10个核心线程:约10MB内存
  - 现代服务器:内存充足,10MB可忽略

收益:
  ✅ 快速响应新任务
  ✅ 减少线程创建开销
  ✅ 提高系统吞吐量
小结:核心线程不会被回收的原因:
  • 源码层面

    • getTask() 中,核心线程使用 workQueue.take() 永久阻塞
    • 非核心线程使用 workQueue.poll(keepAliveTime) 超时返回
    • 只有返回 null 时线程才会退出
  • 设计层面

    • 减少线程创建/销毁开销
    • 提高响应速度
    • 保证基本处理能力
  • 特殊情况

    • 设置 allowCoreThreadTimeOut(true) 后,核心线程也会超时回收

1.3 线程池如何实现线程复用

复制代码
通过Worker机制实现:
   1. Worker继承AQS,实现Runnable
   2. Worker持有Thread和firstTask
   3. 线程执行完任务后,不会销毁
   4. 通过while循环不断从队列取任务执行
   5. 只有在队列为空且超时时才会退出循环
1.3.1 底层源码剖析

1. Worker 内部类(核心载体)

Java 复制代码
private final class Worker extends AbstractQueuedSynchronizer implements Runnable {
    final Thread thread;  // 🔑 真正的工作线程
    Runnable firstTask;   // 第一个任务(可能为 null)

    Worker(Runnable firstTask) {
        this.firstTask = firstTask;
        this.thread = getThreadFactory().newThread(this); // 🔑 传入 this
    }

    public void run() {
        runWorker(this); // 🔑 线程启动后执行这里
    }
}

🔍 关键点:线程启动后执行的是 Worker.run() ,而 Worker.run() 调用的是 runWorker(this) ,这是一个 无限循环获取任务并执行 的方法。

2. runWorker:线程复用的核心逻辑

JAVA 复制代码
final void runWorker(Worker w) {
    Thread wt = Thread.currentThread();
    Runnable task = w.firstTask;
    w.firstTask = null;

    try {
        // 🔑 核心循环:线程复用的本质
        while (task != null || (task = getTask()) != null) {
            w.lock(); // 加锁,防止中断

            try {
                beforeExecute(wt, task); // 前置钩子
                task.run();            // 🔑 直接调用任务的 run 方法
                afterExecute(task, null); // 后置钩子
            } finally {
                task = null;           // 任务执行完置空
                w.completedTasks++;
                w.unlock();
            }

            // 🔑 循环继续,继续获取下一个任务
        }
    } finally {
        processWorkerExit(w, completedAbruptly); // 线程退出处理
    }
}

🔍 关键点:

  • task.run() 是普通方法调用, 不会创建新线程
  • 一个 Worker.thread 会 循环执行多个任务的 run() 方法
  • 线程不会销毁,直到 getTask() 返回 null

3. 线程复用 vs 线程销毁对比

text 复制代码
传统方式(每次创建新线程):
  任务1到达 → 创建线程1 → 执行任务1 → 销毁线程1
  任务2到达 → 创建线程2 → 执行任务2 → 销毁线程2
  开销:每次任务都需要创建/销毁线程

线程池方式(线程复用):
  任务1到达 → 创建线程1 → 执行任务1
  任务2到达 → 线程1复用 → 执行任务2
  任务3到达 → 线程1复用 → 执行任务3
  ...
  开销:只创建一次线程,后续任务复用
1.3.2. 为什么不是 Thread.start()?

new Thread(task).start() 方式, 会创建新线程,不复用线程

worker.thread + task.run()方式,不会创建新线程,复用线程

✅ 线程池的复用本质: 固定线程 + 循环执行任务的 run() 方法

小结:线程复用的底层原理一句话

线程池创建固定数量的 Worker 线程,这些线程启动后不会销毁,而是通过 while (task = getTask()) 循环从队列中取出任务并执行 task.run() ,从而实现一个线程顺序执行多个任务,达到线程复用的目的。

1.4: 为什么先放队列而不是先创建线程?

1.4.1 源码执行顺序分析
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);  // 确保至少有一个线程处理队列
    }
    // 🔑 步骤3: 队列满了才创建非核心线程
    else if (!addWorker(command, false))
        reject(command);
}

执行顺序

  1. 先检查核心线程数,不足则创建核心线程
  2. 先尝试放入队列(关键设计)
  3. 队列满时才创建非核心线程
1.4.2 性能开销对比
复制代码
创建线程的完整过程:

1. 内存分配:
   - 栈空间:默认1MB(可通过-Xss调整)
   - 线程本地存储(TLS)
   - 线程控制块(TCB)
   - 开销:约0.5-2ms

2. 系统调用:
   - 创建内核线程(clone系统调用)
   - 设置线程属性
   - 注册到内核调度器
   - 开销:约1-5ms

3. 初始化:
   - 初始化线程上下文
   - 设置栈指针
   - 初始化寄存器
   - 开销:约0.5-2ms

总开销:约2-10ms(取决于系统负载)

队列操作(LinkedBlockingQueue.offer()):

1. 内存操作:
   - 创建Node节点(约24字节)
   - 设置指针引用
   - 更新队列计数器
   - 开销:纳秒级(<100ns)

2. 同步操作:
   - CAS操作(compareAndSwap)
   - 内存屏障
   - 开销:纳秒级(<100ns)

总开销:约100-500ns(比创建线程快20000倍!)

性能对比表

操作 时间开销 CPU开销 内存开销 系统调用
创建线程 2-10ms 1MB+ 需要
队列操作 100-500ns 极低 24字节 不需要
性能比 20000:1 - - -
1.4.3 设计优势深度解析

优势1: 充分利用已有线程

复制代码
场景:核心线程数=10,当前有5个线程在执行任务
任务到达 → 放入队列 → 空闲的5个线程立即从队列取任务执行
结果:充分利用已有线程,无需创建新线程 ✅

优势2: 减少线程创建开销

复制代码
场景:100个短任务连续到达
先放队列:创建10个核心线程,任务在队列中排队
先创建线程:可能需要创建100个线程(如果允许)
结果:节省90次线程创建,节省约180-900ms ✅

优势3: 平滑处理峰值

复制代码
场景:突然有1000个任务到达
先放队列:10个线程处理,990个任务在队列中等待
先创建线程:可能创建1000个线程(如果允许),系统压力大
结果:平滑处理,避免系统过载 ✅

优势4: 资源可控

复制代码
场景:队列容量500,最大线程数20
先放队列:最多20个线程 + 500个任务在队列
先创建线程:可能创建大量线程,资源不可控
结果:资源使用可控,避免OOM ✅
1.4.4 如果反过来会怎样?

假设先创建线程,后放队列:

java 复制代码
// ❌ 假设的执行流程(错误设计)
public void execute(Runnable command) {
    // 步骤1: 先创建线程
    if (workerCountOf(c) < maximumPoolSize) {
        addWorker(command, false);  // 创建线程
        return;
    }
    
    // 步骤2: 线程满了才放队列
    if (workQueue.offer(command)) {
        // 放入队列
    } else {
        reject(command);
    }
}

问题

  1. 资源浪费:短时任务高峰会创建大量线程,任务结束后线程空闲
  2. 响应延迟:创建线程需要时间,无法立即执行
  3. 系统压力:频繁创建/销毁线程增加系统负载
  4. 无法复用:已有线程可能空闲,却创建新线程
小结

线程池采用先放队列、后创建线程的策略,主要基于性能、资源利用和系统稳定性。性能方面,队列操作(如 LinkedBlockingQueue.offer())是内存操作,耗时约 100-500 纳秒;创建线程需要分配栈空间(默认约 1MB)、系统调用(如 clone)、初始化线程上下文,总耗时约 2-10 毫秒,性能差距约 20000 倍。资源利用上,先放队列能充分利用已有线程:核心线程执行完任务后会从队列取新任务,避免频繁创建/销毁;队列作为缓冲,可平滑处理任务峰值,避免短时突发导致大量线程创建后又快速空闲。从源码看,execute() 先检查核心线程数,不足则创建核心线程;然后尝试放入队列;队列满时才创建非核心线程。这种设计遵循生产者-消费者模式,解耦生产与消费速度,在保证响应性的同时,减少线程创建/销毁开销,提高系统稳定性和资源利用率。如果反过来先创建线程,会导致资源浪费、响应延迟、系统压力增大和无法复用已有线程等问题。因此,先放队列是线程池的核心设计决策,在性能、资源利用和系统稳定性之间取得平衡。

1.5: 如何合理设置线程池参数?

复制代码
调优步骤:

1. 监控关键指标
   - 线程池大小
   - 队列长度
   - 任务执行时间
   - 任务失败率

2. 分析瓶颈
   - 如果队列经常满 → 增加线程数或队列容量
   - 如果线程经常空闲 → 减少线程数
   - 如果任务执行时间长 → 优化任务逻辑

3. 调整参数
   - 根据监控数据逐步调整
   - 每次只调整一个参数
   - 观察效果后再继续调整

4. 验证效果
   - 对比调整前后的性能指标
   - 确保没有引入新问题

调优策略决策表:

队列使用率 线程利用率 CPU使用率 调整动作
> 80% 任意 < 70% 增加核心线程
< 30% < 30% 任意 减少核心线程
< 50% 任意 > 80% 减少核心线程
> 50% 任意 < 30% 增加核心线程
> 70% = 100% < 70% 增加最大线程

1.6: 为什么不推荐使用Executors?

java 复制代码
// ❌ FixedThreadPool:使用无界队列
ExecutorService executor = Executors.newFixedThreadPool(10);
// 问题:LinkedBlockingQueue无界,可能导致OOM

// ❌ CachedThreadPool:最大线程数无限
ExecutorService executor = Executors.newCachedThreadPool();
// 问题:maximumPoolSize = Integer.MAX_VALUE,可能创建大量线程

// ❌ SingleThreadExecutor:无界队列
ExecutorService executor = Executors.newSingleThreadExecutor();
// 问题:LinkedBlockingQueue无界,可能导致OOM

// 推荐:手动创建ThreadPoolExecutor,明确参数含义

1.7: 线程池的拒绝策略如何选择?

java 复制代码
// 1. AbortPolicy(默认):抛出异常
new ThreadPoolExecutor.AbortPolicy()
// 抛出 RejectedExecutionException
// 适用:需要明确知道任务被拒绝

// 2. CallerRunsPolicy:调用者执行(项目使用)
new ThreadPoolExecutor.CallerRunsPolicy()
// 由提交任务的线程执行任务
// 适用:需要背压机制,防止系统过载 ✅

// 3. DiscardPolicy:直接丢弃
new ThreadPoolExecutor.DiscardPolicy()
// 静默丢弃,不抛异常
// 适用:允许丢失任务,追求性能

// 4. DiscardOldestPolicy:丢弃最老任务
new ThreadPoolExecutor.DiscardOldestPolicy()
// 丢弃队列中最老的任务,然后重试提交
// 适用:新任务优先级更高

1.8: 如何优雅关闭线程池?

复制代码
1. 调用shutdown(),不再接受新任务
2. 调用awaitTermination(),等待已提交任务完成
3. 如果超时,调用shutdownNow()强制关闭
4. 再次awaitTermination(),确保关闭完成

优雅关闭线程池 = "先软后硬" 两步走:

  1. 先 shutdown() 让线程池 体面下班 ;
  2. 再 awaitTermination() 限时等待,超时后 shutdownNow() 强制清场 。
1.8.1 模板代码(生产级)
java 复制代码
public static void gracefulShutdown(ThreadPoolExecutor pool) {
    // 1. 先软关闭:不再接收新任务,但已入队的会继续执行
    pool.shutdown();

    try {
        // 2. 设置最大等待时间(根据业务调整)
        if (!pool.awaitTermination(60, TimeUnit.SECONDS)) {
            // 3. 超时后硬关闭:尝试中断正在执行的任务
            List<Runnable> dropList = pool.shutdownNow();
            log.warn("线程池强制关闭,丢弃任务数:{}", dropList.size());

            // 4. 再等待一次,确保真正结束
            if (!pool.awaitTermination(30, TimeUnit.SECONDS)) {
                log.error("线程池仍未完全终止,可能存在阻塞任务");
            }
        }
    } catch (InterruptedException e) {
        // 5. 当前线程被中断,立即强制关闭
        pool.shutdownNow();
        Thread.currentThread().interrupt(); // 保持中断状态
    }
}
一行总结

先 shutdown() 给任务体面执行的机会,再 awaitTermination() 限时等待,超时后 shutdownNow() 强制中断并记录丢弃任务,全程捕获中断并恢复标志位------这就是优雅关闭线程池的全部秘诀。

1.9: 线程池的生命周期有哪些状态,状态之间如何切换;

🎯 五种状态详解

  1. RUNNING(运行状态) 🟢

场景:

  • 线程池创建后的初始状态
  • 可以通过 execute() 或 submit() 提交任务
  1. SHUTDOWN(关闭状态) 🟡

场景:

  • 需要优雅关闭线程池
  • 希望处理完队列中的所有任务
  • 应用正常关闭流程

触发方式:

java 复制代码
executor.shutdown();

状态转换:

  • RUNNING → SHUTDOWN:调用 shutdown()
  • SHUTDOWN → TIDYING:当队列为空且所有工作线程都终止时
  1. STOP(停止状态) 🔴

触发方式:

JAVA 复制代码
List<Runnable> notExecutedTasks = executor.shutdownNow();

场景:

  • 需要立即停止线程池
  • 不关心队列中未执行的任务
  • 强制中断正在执行的任务

状态转换:

  • RUNNING → STOP:调用 shutdownNow()
  • SHUTDOWN → STOP:在 SHUTDOWN 状态时调用 shutdownNow()
  • STOP → TIDYING:所有工作线程都终止时
  1. TIDYING(整理状态) 🟠

触发条件:

  • 从 SHUTDOWN 状态:队列为空 && 所有工作线程终止
  • 从 STOP 状态:所有工作线程终止

状态转换:

  • SHUTDOWN/STOP → TIDYING:满足终止条件时自动转换
  • TIDYING → TERMINATED:执行完 terminated() 方法后

说明:

  • 这是一个自动过渡状态,无法手动触发
  • 在此状态会调用 terminated() 钩子方法
  1. TERMINATED(终止状态) ⚫

到达条件:

  • terminated() 方法执行完成

场景:

  • 线程池生命周期结束
  • awaitTermination() 方法会在此状态返回 true

🔀 完整的状态转换路径

路径1:优雅关闭

text 复制代码
RUNNING 
   ↓ shutdown()
SHUTDOWN (处理完队列任务)
   ↓ 队列空 && 工作线程=0
TIDYING (执行 terminated())
   ↓ terminated() 完成
TERMINATED

路径2:强制关闭

text 复制代码
RUNNING 
   ↓ shutdownNow()
STOP (中断所有任务)
   ↓ 工作线程=0
TIDYING (执行 terminated())
   ↓ terminated() 完成
TERMINATED

路径3:先优雅后强制

text 复制代码
RUNNING 
   ↓ shutdown()
SHUTDOWN (等待超时)
   ↓ shutdownNow()
STOP (中断任务)
   ↓ 工作线程=0
TIDYING (执行 terminated())
   ↓ terminated() 完成
TERMINATED

⚠️ 注意事项

  1. SHUTDOWN vs STOP
  • shutdown():温和,等待任务完成
  • shutdownNow():粗暴,立即中断
  1. 中断响应
  • 任务需要正确处理 InterruptedException
  • 使用 Thread.interrupted() 检查中断状态
  1. 资源清理
  • 重写 terminated() 方法进行清理
  • 确保外部资源正确关闭
  1. 避免状态误判
  • isShutdown() 在 SHUTDOWN、STOP、TIDYING、TERMINATED 状态都返回 true
  • isTerminated() 只在 TERMINATED 状态返回 true

1.10: 线程池的任务提交有两种,execute() 、submit(), 有啥区别?

JAVA 复制代码
public void execute(Runnable command) {
    if (command == null) throw new NullPointerException();
    // 直接走 execute()
    ...
}

public <T> Future<T> submit(Callable<T> task) {
    if (task == null) throw new NullPointerException();
    RunnableFuture<T> ftask = newTaskFor(task);  // ① 包装 FutureTask
    execute(ftask);                              // ② 仍然调 execute()
    return ftask;
}

submit 本质 = newTaskFor() + execute() ;差异在包装层,不在调度层。( submit 的底层仍然是 execute ,只是多穿了一件 FutureTask 外衣。)

异常:execute 被线程池吃,submit 被 Future 囤,get 时才吐。

相关推荐
百***06012 小时前
SpringBoot的@Scheduled和@Schedules有什么区别
java·spring boot·spring
喵了几个咪3 小时前
使用Bazel构建你的Kratos微服务
java·运维·微服务
千寻技术帮3 小时前
50022_基于微信小程序同城维修系统
java·mysql·微信小程序·小程序·同城维修
九年义务漏网鲨鱼3 小时前
【大模型面经】千问系列专题面经
人工智能·深度学习·算法·大模型·强化学习
野蛮人6号3 小时前
黑马八股笔记
java
Charles_go3 小时前
41、C#什么是单例设计模式
java·设计模式·c#
皮皮林5514 小时前
别再只会 mvn install 了!深入拆解 Maven 插件核心原理
java·maven
源码之家4 小时前
机器学习:基于大数据二手房房价预测与分析系统 可视化 线性回归预测算法 Django框架 链家网站 二手房 计算机毕业设计✅
大数据·算法·机器学习·数据分析·spark·线性回归·推荐算法
百***49004 小时前
SpringSecurity的配置
java