线程池shutdown和shutdownNow原理和区别

Posted wen-pan

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了线程池shutdown和shutdownNow原理和区别相关的知识,希望对你有一定的参考价值。

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


一、shutdown流程

1、流程简介

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

二、shutdownNow流程

1、流程简介

  • 修改线程池状态为SHUTDOWN
  • 不再接收任务提交
  • 尝试中断线程池中所有的线程(包括正在执行的线程)
  • 返回正在等待执行的任务列表 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!!!!

以上是关于线程池shutdown和shutdownNow原理和区别的主要内容,如果未能解决你的问题,请参考以下文章

关闭线程池 shutdown 和 shutdownNow 的区别

原 线程池中shutdown()和shutdownNow()方法的区别

关闭线程池 shutdown 和 shutdownNow 的区别

关闭线程池 shutdown 和 shutdownNow 的区别

关闭线程池 shutdown 和 shutdownNow 的区别?

JAVA线程池shutdown和shutdownNow的区别