对于我们使用的线程池 ThreadPoolExecutor 来说,停止线程池的方法有以下两个:

  1. shutdown():优雅的关闭线程池,即不再接受新任务,但会等待已提交任务(包括正在执行的任务和在队列中等待的任务)执行完毕。等待所有任务都执行完毕后,线程池才会进入终止状态
  2. shutdownNow():尝试停止所有正在执行的任务,并返回等待执行的任务列表。需要立即停止线程池,但不关心正在执行的任务是否立即完成的情况下。

1.代码演示

下面通过代码案例,咱们来了解一下 shutdown() 和 shutdownNow() 方法的具体使用。

1.1 shutdown() 方法执行

我们将线程池核心和最大线程数都设置为 2,任务队列可以存储 10 个任务,一次性添加了 5 个任务,每个任务执行 2s 以上,添加完任务之后执行停止方法,并在 1s 之后尝试添加另一个新任务,如下代码所示:

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.RejectedExecutionHandler;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class ThreadPoolExecutorShutdownTest {
    public static void main(String[] args) {
        // 创建线程
        ThreadPoolExecutor executor = new ThreadPoolExecutor(
                2,
                2,
                1000,
                TimeUnit.MILLISECONDS,
                new ArrayBlockingQueue<Runnable>(10),
                new RejectedExecutionHandler() {
                    @Override
                    public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
                        System.out.println("执行拒绝策略");
                    }
                });
        // 添加任务
        for (int i = 0; i < 5; i++) {
            executor.submit(() -> {
                String tName = Thread.currentThread().getName();
                System.out.println(tName + ":开始执行任务!");
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(tName + ":结束执行任务!");
            });
        }
        // 停止线程
        executor.shutdown();
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        // 添加新任务
        executor.submit(() -> System.out.println("最后一个新任务"));
    }
}

以上程序的执行结果如下:

从以上结果可以看出,执行 shutdown() 方法后,程序会等待线程池中的所有任务全部执行完在关闭,再次期间线程池会拒绝加入新任务,并调用线程池的拒绝策略

1.2 shutdownNow()方法执行

如果将 shutdown() 方法换成 shutdownNow() 方法后,以上程序的执行结果如下:

也就是说,调用 shutdownNow() 之后,正在执行的任务会被立即停止,且任务队列中未执行的任务也会被清除,调用 shutdownNow() 方法后新加入的任务会被拒绝,并执行线程池的拒绝策略

2.shutdown()执行流程

shutdown() 方法执行源码如下:

public void shutdown() {
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        checkShutdownAccess();
        advanceRunState(SHUTDOWN);
        interruptIdleWorkers();
        onShutdown(); // hook for ScheduledThreadPoolExecutor
    } finally {
        mainLock.unlock();
    }
    tryTerminate();
}

该源码执行流程如下:

  1. 加锁:在多线程环境下,关闭操作涉及到修改关键状态和执行一些可能影响多个线程的操作。使用锁可以确保这些操作的原子性和一致性,避免多个线程同时进行关闭操作导致数据不一致或出现意外情况
  2. 检查关闭权限:在关闭之前进行状态检查可以确保关闭操作是合法的,避免在不适当的时候进行关闭。推进状态可以让其他代码部分能够根据当前执行器的状态做出正确的反应。
  3. 将状态设置为 SHUTDOWN:阻止新任务提交但完成现有任务。
  4. 中断空闲线程
  5. 调用 onShutdown 方法(钩子方法)
  6. 尝试终止线程池:如果所有任务已完成的情况下,会真正的终止线程池。

shutdown() 方法的执行流程如下图所示:

课后思考

为什么需要关闭线程池?关闭线程池的场景有哪些?说说 shutdownNow() 的执行流程?

特殊说明

以上内容来自我的《Java 面试突击训练营》,这门课程是有着十几年工作经验(前 360 开发工程师),10 年面试官经验的我,花费 4 年时间打磨完成的一门视频面试课。学完训练营的课程之后,基本可以应对目前市面上绝大部分公司的面试了,并且课程配备了 9 大就业服务,帮助上千人找到 Java 工作,其中上百人拿到大厂 Offer,学员最高薪资 70W 年薪,面试课目录和 9 大服务如下:

加我微信咨询:vipStone【备注:训练营】