写点什么

面试官:说一说如何优雅的关闭线程池,我:shutdownNow,面试官:粗鲁!

  • 2024-06-03
    福建
  • 本文字数:2811 字

    阅读完需:约 9 分钟

优雅的关闭线程池


我们现在步入正题,来看一看在线程池使用完成后如何优雅的关闭线程池。


在 JDK 1.8 中,Java 并发工具包中 java.util.concurrent.ExecutorService 提供了 shutdown()、shutdownNow()这两种接口方法去关闭线程池,我们分别看一下。


shutdown()

public void shutdown() {    final ReentrantLock mainLock = this.mainLock; // ThreadPoolExecutor的主锁    mainLock.lock(); // 加锁以确保独占访问     try {        checkShutdownAccess(); // 检查是否有关闭的权限        advanceRunState(SHUTDOWN); // 将执行器的状态更新为SHUTDOWN        interruptIdleWorkers(); // 中断所有闲置的工作线程        onShutdown(); // ScheduledThreadPoolExecutor中的挂钩方法,可供子类重写以进行额外操作    } finally {        mainLock.unlock(); // 无论try块如何退出都要释放锁    }    tryTerminate(); // 如果条件允许,尝试终止执行器}
复制代码


在 shutdown 的源码中,会启动一次顺序关闭,在这次关闭中,执行器不再接受新任务,但会继续处理队列中的已存在任务,当所有任务都完成后,线程池中的线程会逐渐退出。


我们写一个小的 demo 来使用 shutdown():

public class TestService{    public static void main(String[] args) {        //创建固定 3 个线程的线程池,测试使用,工作中推荐ThreadPoolExecutor        ExecutorService threadPool = Executors.newFixedThreadPool(3);         //向线程池提交 10 个任务        for (int i = 1; i <= 10; i++) {            final int index = i;            threadPool.submit(() -> {                System.out.println("正在执行任务 " + index);                //休眠 3 秒,模拟任务执行                try {                    Thread.sleep(3000);                } catch (InterruptedException e) {                    e.printStackTrace();                }            });        }         //休眠 4 秒        try {            Thread.sleep(4000);        } catch (InterruptedException e) {            e.printStackTrace();        }         //关闭线程池        threadPool.shutdown();    }}
复制代码


在这段测试代码中,我们构造了一个包含固定 3 线程数的线程池,循环提交 10 个任务,每个任务休眠 3 秒,但主程序休眠 4 秒后,会掉用 shutdown 方法,理论上,在第二个时间循环中,线程池被停止,所以最多执行完 6 个任务,但从输出中,我们丝毫感受不好线程何时被停止了。

输出:

正在执行任务 1正在执行任务 3正在执行任务 2正在执行任务 4正在执行任务 5正在执行任务 6正在执行任务 7正在执行任务 8正在执行任务 9正在执行任务 10
复制代码


shutdownNow()

/** * 尝试停止所有正在执行的任务,停止处理等待的任务, * 并返回等待处理的任务列表。 * * @return 从未开始执行的任务列表 */public List<Runnable> shutdownNow() {    List<Runnable> tasks; // 用于存储未执行的任务的列表    final ReentrantLock mainLock = this.mainLock; // ThreadPoolExecutor的主锁    mainLock.lock(); // 加锁以确保独占访问    try {        checkShutdownAccess(); // 检查是否有关闭的权限        advanceRunState(STOP); // 将执行器的状态更新为STOP        interruptWorkers(); // 中断所有工作线程        tasks = drainQueue(); // 清空队列并将结果放入任务列表中    } finally {        mainLock.unlock(); // 无论try块如何退出都要释放锁    }    tryTerminate(); // 如果条件允许,尝试终止执行器        return tasks; // 返回队列中未被执行的任务列表}
复制代码


与 shutdown 不同的是 shutdownNow 会尝试终止所有的正在执行的任务,清空队列,停止失败会抛出异常,并且返回未被执行的任务列表。


由于 shutdownNow 会有返回值,所以我们将上面的测试案例稍作改动后输出结果为:



这种会在控制台抛出异常的方式,同样也不优雅,所以我们继续往下看!


shutdown()+awaitTermination(long timeout, TimeUnit unit)


awaitTermination(long timeout, TimeUnit unit)是可以允许我们在调用 shutdown 方法后,再设置一个等待时间,如设置为 5 秒,则表示 shutdown 后 5 秒内线程池彻底终止,返回 true,否则返回 false;

这种方式里,我们将 shutdown()结合 awaitTermination(long timeout, TimeUnit unit)方法去使用,注意在调用 awaitTermination() 方法时,应该设置合理的超时时间,以避免程序长时间阻塞而导致性能问题,而且由于这个方法在超时后也会抛出异常,因此,我们在使用的时候要捕获并处理异常!

public class TestService{    public static void main(String[] args) {        //创建固定 3 个线程的线程池        ExecutorService threadPool = Executors.newFixedThreadPool(3);         //向线程池提交 10 个任务        for (int i = 1; i <= 10; i++) {            final int index = i;            threadPool.submit(() -> {                System.out.println("正在执行任务 " + index);                //休眠 3 秒                try {                    Thread.sleep(3000);                } catch (InterruptedException e) {                    e.printStackTrace();                }            });        }        //关闭线程池,设置等待超时时间 3 秒        System.out.println("设置线程池关闭,等待 3 秒...");        threadPool.shutdown();        try {            boolean isTermination = threadPool.awaitTermination(3, TimeUnit.SECONDS);            System.out.println(isTermination ? "线程池已停止" : "线程池未停止");        } catch (InterruptedException e) {            e.printStackTrace();        }         //再等待超时时间 20 秒        System.out.println("再等待 20 秒...");        try {            boolean isTermination = threadPool.awaitTermination(20, TimeUnit.SECONDS);            System.out.println(isTermination ? "线程池已停止" : "线程池仍未停止,请检查!");        } catch (InterruptedException e) {            e.printStackTrace();        }    }}
复制代码


输出:

设置线程池关闭,等待 3 秒...正在执行任务 1正在执行任务 2正在执行任务 3正在执行任务 4正在执行任务 5线程池未停止再等待 20 秒...正在执行任务 6正在执行任务 7正在执行任务 8正在执行任务 9正在执行任务 10线程池已停止
复制代码


从输出中我们可以看到,通过将两种方法结合使用,我们监控了整个线程池关闭的全流程,实现了优雅的关闭!


文章转载自:JavaBuild

原文链接:https://www.cnblogs.com/JavaBuild/p/18226822

体验地址:http://www.jnpfsoft.com/?from=infoq

用户头像

还未添加个人签名 2023-06-19 加入

还未添加个人简介

评论

发布
暂无评论
面试官:说一说如何优雅的关闭线程池,我:shutdownNow,面试官:粗鲁!_面试_不在线第一只蜗牛_InfoQ写作社区