线程池shutdown和shutdownNow原理和区别

本文深入解析了Java线程池的shutdown和shutdownNow方法的执行流程及区别。shutdown仅中断空闲线程,允许已提交任务执行完毕;而shutdownNow尝试中断所有线程,包括正在执行的任务,返回未执行的任务列表。线程中断并不强制停止,需配合响应中断的方法。此外,线程池状态变更和任务提交检查确保了线程池在关闭过程中的行为可控。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

说明:以ThreadPoolExecutor线程池为例说明整个流程(不同的线程池实现上略有差别)。


一、shutdown流程

1、流程简介
  • 修改线程池状态为SHUTDOWN
  • 不再接收新提交的任务
  • 中断线程池中空闲的线程
  • 第③步只是中断了空闲的线程,但正在执行的任务以及线程池任务队列中的任务会继续执行完毕

二、shutdownNow流程

1、流程简介
  • 修改线程池状态为STOP
  • 不再接收任务提交
  • 尝试中断线程池中所有的线程(包括正在执行的线程)
  • 返回正在等待执行的任务列表 List<Runnable>

此时线程池中等待队列中的任务不会被执行,正在执行的任务也可能被终止(为什么是可能呢?因为如果正常执行的任务如果不响应中断,那么就不会被终止,直到任务执行完毕)


三、问题说明

①、线程是如何中断的

中断线程池中的线程的方法是通过调用 Thread.interrupt()方法来实现的,这种方法的作用有限,如果线程中没有sleep 、wait、Condition、定时锁等应用, interrupt() 方法是无法中断当前的线程的(因为sleep、condition、await这些是响应中断的)。所以,shutdownNow()并不代表线程池就一定立即就能退出,它也可能必须要等待所有正在执行的任务都执行完成了才能退出。但是大多数时候是能立即退出的。

②、为什么修改线程池状态为shutdown以后线程池就不能接收新任务了

在向线程池提交任务的时候,会先检查线程池状态, 线程池状态为非关闭(或停止)时才能提交任务,这里已经将线程池状态修改为shutdown了,自然就不能接受新的任务提交了,可参考execute(Runnable command)逻辑和 addWorker逻辑)

1、execute方法
public void execute(Runnable command) {
    if (command == null)
        throw new NullPointerException();
  
    int c = ctl.get();
    // 【 1 】、worker数量比核心线程数小,直接创建worker执行任务
    if (workerCountOf(c) < corePoolSize) {
        if (addWorker(command, true))
            return;
        c = ctl.get();
    }
    // 【 2 】、worker数量超过核心线程数,任务直接进入队列。这里进入队列前先判断了线程池状态
    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 】、 如果线程池不是运行状态,或者任务进入队列失败,则尝试创建worker执行任务(即:线程阻塞队列满了但线程池中的线程数没达到最大线程数,
    // 则新开启一个线程去执行该任务)。
    // 这儿有3点需要注意:
    // 1. 线程池不是运行状态时,addWorker内部会判断线程池状态
    // 2. addWorker第2个参数表示是否创建核心线程
    // 3. addWorker返回false,则说明任务执行失败,需要执行reject操作
    else if (!addWorker(command, false))
        reject(command);
}
2、addWorker方法
// 新建一个worker
w = new Worker(firstTask);
int rs = runStateOf(ctl.get());

// 这儿需要重新检查线程池状态,即线程池状态为shutdown以后不能再提交任务了
if (rs < SHUTDOWN || (rs == SHUTDOWN && firstTask == null)) {
  // 添加到工作线程集合中
  workers.add(w);
  int s = workers.size();
  // 更新largestPoolSize变量的值
  if (s > largestPoolSize)
    largestPoolSize = s;
  workerAdded = true;
}

四、源码比较

1、shutdown源码
public void shutdown() {
  final ReentrantLock mainLock = this.mainLock;
  // 获取线程池的锁
  mainLock.lock();
  try {
    // 检查关闭进入许可
    checkShutdownAccess();
    // 将线程池状态改为SHUTDOWN
    advanceRunState(SHUTDOWN);
    // 【中断线程池中空闲线程】,注意和下面的shutdownNow方法中的进行对比
    interruptIdleWorkers();
    // 留给定时任务线程池的钩子方法,这里没有实现,在定时任务线程池中有实现
    onShutdown(); // hook for ScheduledThreadPoolExecutor
  } finally {
    mainLock.unlock();
  }
  
  // ①、如果线程池状态为正在运行 或 已经是 TIDYING 状态以上了 或者  线程池状态为shutdown但是等待队列中还有任务,
  // 那么这个方法什么都不做,直接返回
  // ②、如何线程池中还有未被中断的线程,则这里会再次去中断他(并且利用中断传播 从等待队列中删除等待的worker)
  // ③、如果线程池的状态是shutdown,并且等待队列中已经没有任务了,那么此时会把线程池状态转换为 TIDYING,
  // 并唤醒所有调用awaitTermination()等待线程池关闭的线程
  tryTerminate();
}
2、shutdownNow源码
public List<Runnable> shutdownNow() {
  List<Runnable> tasks;
  // 获取线程池的锁
  final ReentrantLock mainLock = this.mainLock;
  mainLock.lock();
  try {
    // 检查关闭进入许可
    checkShutdownAccess();
    // 将线程池状态改为STOP
    advanceRunState(STOP);
    // 【中断所有线程】
    interruptWorkers();
    // 获取任务队列里未完成任务
    tasks = drainQueue();
  } finally {
    mainLock.unlock();
  }

  // ①、如果线程池状态为正在运行 或 已经是 TIDYING 状态以上了 或者  线程池状态为shutdown但是等待队列中还有任务,
  // 那么这个方法什么都不做,直接返回
  // ②、如何线程池中还有未被中断的线程,则这里会再次去中断他(并且利用中断传播 从等待队列中删除等待的worker)
  // ③、如果线程池的状态是shutdown,并且等待队列中已经没有任务了,那么此时会把线程池状态转换为 TIDYING,
  // 并唤醒所有调用awaitTermination()等待线程池关闭的线程
  tryTerminate();
  return tasks;
}

通过比较shutdown源码和shutdownNow的源码我们可以发现,这两个方法最大的不同在于中断线程的地方:

  • 首先,需要再次明确的一点是,中断线程并不是立即把这个线程停止,而是把这个线程的【中断状态】设置为true,表示有其他线程来中断过这个线程。
  • shutdown方法调用interruptIdleWorkers()方法中断的只是线程池中空闲的线程,那么也就说明线程池中正在工作的线程没有被中断,所以说正在工作的线程会继续执行完毕,并且正在工作的线程也会去任务队列中将已经提交的任务取出来并执行。
  • shutdownNow方法调用的是interruptWorkers()方法,该方法会逐一遍历线程池中的每一个线程(包括空闲线程和正在工作的线程)并且去中断他,所以说当调用shutdownNow方法的时候,所有线程都会被中断,等待队列中的任务不会被执行,正在执行的任务也可能会中断退出(为什么是可能而不是一定?因为如果正在执行的线程不响应中断,那么他就会继续运行)
  • 中断所有线程和中断空闲线程的代码比较,可以看源码interruptIdleWorkers()interruptWorkers()方法的实现,比较简单

上面代码中tryTerminate()方法值得探究也比较有趣,他先中断一个空闲线程,然后通过传播中断信号去中断其他的线程,然后不断地传播给每个worker!!!!

<think>好的,我现在需要准备关于线程池的面试题,包括原理执行过程。首先,我得回忆一下线程池的基本概念作用。线程池是一种多线程处理形式,处理过程中将任务添加到队列,然后在创建线程后自动启动这些任务。使用线程池可以减少在创建销毁线程上花费的开销,提高系统性能。 接下来,我需要理解线程池的核心组成部分。一般来说,线程池包括以下几个部分:核心线程数、最大线程数、工作队列、拒绝策略。核心线程数是线程池中一直存在的线程,即使它们处于空闲状态。最大线程数是线程池允许创建的最大线程数量。工作队列用于存放待处理的任务,当核心线程都在忙时,新任务会被放入队列中。当队列也满了之后,线程池会创建新的线程,直到达到最大线程数。如果此时还有新任务进来,就会触发拒绝策略,比如直接丢弃任务或者抛出异常。 然后,执行过程是怎样的呢?当提交一个新任务到线程池时,线程池的处理流程应该是这样的:首先检查当前运行的线程数是否小于核心线程数,如果是,就创建新的线程来处理任务。否则,将任务放入工作队列。如果队列已满,且当前线程数小于最大线程数,则创建新的线程处理任务。如果队列已满且线程数已达最大值,则根据拒绝策略处理无法执行的任务。 这时候,我需要考虑可能存在的面试问题,比如各个参数的作用,如何合理配置线程池参数,不同的拒绝策略有什么区别,以及线程池的生命周期管理等。例如,核心线程数最大线程数的设置需要考虑系统的负载情况,任务的性质(CPU密集型还是IO密集型),以及硬件资源如CPU核心数。拒绝策略常见的有AbortPolicy(抛出异常)、CallerRunsPolicy(由提交任务的线程执行任务)、DiscardPolicy(直接丢弃任务)、DiscardOldestPolicy(丢弃队列中最旧的任务)。 另外,线程池的生命周期管理也很重要。线程池的状态包括运行中、关闭中、已终止等。关闭线程池可以通过shutdown()或shutdownNow()方法,前者会等待所有任务完成,后者会尝试中断正在执行的任务。 还有,线程池中的线程是如何复用的?通常,线程池中的线程会从工作队列中不断取出任务执行,当没有任务时,线程会等待一定时间,超过时间后可能会被回收,具体取决于线程池的配置。 可能还需要了解常见的线程池类型,比如FixedThreadPool、CachedThreadPool、ScheduledThreadPool等,它们各自的适用场景内部实现机制。例如,FixedThreadPool有固定数量的核心线程,适用于负载较重的服务器;CachedThreadPool则根据需要创建新线程,适用于执行很多短期异步任务。 此外,线程池的工作队列也有不同的实现,比如有界队列(如ArrayBlockingQueue)无界队列(如LinkedBlockingQueue),以及同步移交队列(如SynchronousQueue)。不同的队列类型会影响线程池的行为,例如使用无界队列可能会导致内存耗尽,而有界队列则需要合理设置容量。 最后,实际应用中可能遇到的线程池问题,比如任务堆积导致内存溢出,线程死锁,或者线程泄漏等,以及如何监控调优线程池。例如,可以通过监控线程池的活动线程数、任务队列大小、完成任务数等指标来调整参数,或者使用ThreadPoolExecutor提供的钩子方法进行扩展。 总结下来,线程池的面试题需要涵盖其原理、组成部分、执行流程、参数配置、拒绝策略、生命周期、常见类型及队列选择,以及问题排查调优等方面。需要结合实际场景举例说明,才能更全面地展示理解应用能力。</think>以下是关于线程池的面试核心知识点整理,涵盖原理、执行流程、关键配置及典型问题: --- ### **线程池核心组成(7大参数)** ```java ThreadPoolExecutor( int corePoolSize, // 核心线程数(常驻线程) int maximumPoolSize, // 最大线程数(弹性扩容上限) long keepAliveTime, // 空闲线程存活时间(非核心线程) TimeUnit unit, // 时间单位 BlockingQueue<Runnable> workQueue, // 任务队列 ThreadFactory threadFactory, // 线程创建工厂 RejectedExecutionHandler handler // 拒绝策略 ) ``` --- ### **执行流程(四步处理机制)** 1. **核心线程处理** - 新任务提交时,优先使用核心线程(若核心线程未满) 2. **任务入队** - 核心线程全忙时,任务进入阻塞队列等待 3. **扩容线程** - 当队列已满且线程数 < `maximumPoolSize`,创建新线程处理 4. **拒绝策略触发** - 队列满且线程数达上限时,执行拒绝策略 --- ### **关键参数配置原则** | 参数类型 | 配置要点 | 示例场景 | |-------------------|---------------------------------------------|-----------------------| | `corePoolSize` | CPU密集型:$N_{cpu}+1$;IO密集型:$2N_{cpu}$ | 8核服务器设置9-16 | | `maximumPoolSize` | 根据任务特性弹性扩展,避免过高导致上下文切换 | 通常设为`2 * corePoolSize` | | `workQueue` | 有界队列防OOM;无界队列需评估最大堆积量 | `ArrayBlockingQueue(200)` | --- ### **四大拒绝策略对比** | 策略类 | 行为描述 | 适用场景 | |-----------------------|----------------------------------|----------------------| | `AbortPolicy` | 直接抛出`RejectedExecutionException` | 需严格保证任务不丢失 | | `CallerRunsPolicy` | 由提交任务的线程直接执行 | 降级为同步处理 | | `DiscardPolicy` | 静默丢弃新提交的任务 | 允许容忍部分数据丢失 | | `DiscardOldestPolicy` | 丢弃队列头部的任务并重试提交 | 优先处理最新任务 | --- ### **线程复用机制(关键实现)** 1. **Worker线程封装** - 每个Worker继承`AQS`,实现锁机制与任务执行循环 2. **任务获取逻辑** - `getTask()`方法从队列中获取任务,支持超时控制 3. **线程回收** - 非核心线程在`keepAliveTime`空闲后被回收 --- ### **常见线程池类型(Executors工具类)** | 线程池类型 | 实现原理 | 潜在风险 | |----------------------|---------------------------------------|-------------------------| | `newFixedThreadPool` | 固定核心线程+无界队列(`LinkedBlockingQueue`) | 队列堆积导致OOM | | `newCachedThreadPool`| 线程数无上限+同步队列(`SynchronousQueue`) | 线程爆炸引发高负载 | | `newSingleThreadExecutor` | 单线程+无界队列 | 同上无界队列问题 | | `newScheduledThreadPool` | 支持定时/周期性任务执行 | 任务堆积需谨慎 | --- ### **高频面试问题** 1. **线程池如何实现线程复用?** - *答:通过Worker线程循环从队列获取任务执行,而非频繁创建销毁* 2. **核心线程能否被回收?** - *答:默认不回收,可通过`allowCoreThreadTimeOut(true)`开启* 3. **提交`execute()`与`submit()`区别?** - *答:`submit()`返回`Future`对象,支持获取执行结果或异常* 4. **线程池关闭的正确方式?** - *答:`shutdown()`平滑关闭;`shutdownNow()`立即中断(返回未执行任务列表)* 5. **如何监控线程池状态?** - *答:通过`getActiveCount()`、`getQueue().size()`等API,或自定义`beforeExecute()`钩子* --- ### **生产环境最佳实践** 1. **禁止使用Executors创建线程池** - 推荐手动配置`ThreadPoolExecutor`,明确资源边界 2. **异常处理策略** - 在任务代码外层包裹`try-catch`,或实现`UncaughtExceptionHandler` 3. **动态调参方案** - 借助`setCorePoolSize()`、`setMaximumPoolSize()`实现运行时调整 --- **总结**:深入理解线程池的**任务调度机制**、**资源控制策略****异常处理逻辑**,是应对相关面试问题的关键。建议结合源码分析(如Worker类的`runWorker()`方法)加深理解。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值