首页 > 其他分享 >面试官:说一说如何优雅的关闭线程池,我:shutdownNow,面试官:粗鲁!

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

时间:2024-06-02 10:00:03浏览次数:23  
标签:面试官 正在 关闭 shutdownNow 任务 线程 shutdown 执行

写在开头


面试官:“小伙子,线程池使用过吗,来聊一聊它吧!”

我:“好的,然后巴拉巴拉一顿输出之前看过的build哥线程池十八问...”

面试官满意的点了点头,紧接着问道:“那你知道如何优雅的关闭线程池吗?”

我:“知道知道,直接调用shutdownNow()方法就好了呀!”

面试官脸色一变,微怒道:“粗鲁!你给我滚出去!!!”


优雅的关闭线程池

哈哈,上面的场景是build哥臆想出来的面试画面,我们现在步入正题,来看一看在线程池使用完成后如何优雅的关闭线程池。

在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会有返回值,所以我们将上面的测试案例稍作改动后输出结果为:

image

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

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
线程池已停止

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

结尾彩蛋

如果本篇博客对您有一定的帮助,大家记得留言+点赞+收藏呀。原创不易,转载请联系Build哥!
image

如果您想与Build哥的关系更近一步,还可以关注“JavaBuild888”,在这里除了看到《Java成长计划》系列博文,还有提升工作效率的小笔记、读书心得、大厂面经、人生感悟等等,欢迎您的加入!
image

标签:面试官,正在,关闭,shutdownNow,任务,线程,shutdown,执行
From: https://www.cnblogs.com/JavaBuild/p/18226822

相关文章

  • 利用Linux系统提供的和调度器相关的接口让进程或线程对某个处理器进行绑定
    目录设置进程与CPU的亲和性设置线程与CPU的亲和性设置进程与CPU的亲和性taskset命令允许你查看或设置运行中的进程的CPU亲和性(即该进程可以在哪些CPU上运行)。要将一个已经运行的进程(例如PID为1234的进程)绑定到CPU0和CPU1上,你可以使用:taskset-cp0,11234如果你正在启动一......
  • C++多线程原理详解
    学习C++多线程时,我有如下疑问:mutex的lock和unlock做了什么?mutex、lock_guard、unique_lock,它们之间的关系是什么?condition_variable中的wait做了什么?带着这些疑问,我查阅了一些资料,整理出本文。文章目录一、mutex二、lock_guard三、unique_lock四、condition......
  • Linux线程之读写锁小结
    读写锁(rwlock)与互斥锁(MutexLock)相比,有如下特点:更加细致的区分了读和写,给共享资源分别上了“读”锁和“写”锁。“写”独占,“读”共享,“写”锁优先级更高如果共享资源在绝大多数情况下是“读”操作,可以提高程序的并发性能。常用的函数原型总结如下:pthread_rwlock_trwlock......
  • 单例模式线程安全reorder问题
    单例模式是一种常用的软件设计模式,它确保一个类只有一个实例,并提供一个全局访问点来获取这个实例。下面是一个使用C++实现的线程安全的单例模式的例子:classSingleton{private:staticstd::atomic<Singleton*>instance;//静态私有实例指针使用原子操作类atomic线程安全sta......
  • [转帖]在Linux上查看活跃线程数与连接数
     https://www.cnblogs.com/xibuhaohao/p/11413669.html 原创:扣钉日记(微信公众号ID:codelogs),欢迎分享,非公众号转载保留此声明。简介现如今,有两种常见的软件资源几乎成了Java后端程序的标配,即线程池与连接池,但这些池化资源非常的重要,一旦不够用了,就会导致程序阻塞、性能......
  • Linux系统编程之线程
    一.线程介绍1.进程与线程典型的UNIX/Linux进程可以看成只有一个控制线程:一个进程在同一时刻只做一件事情。有了多个控制线程后,在程序设计时可以把进程设计成在同一时刻做不止一件事,每个线程各自处理独立的任务。进程是程序执行时的一个实例,是担当分配系统资源(CP......
  • Java多线程编程:提高程序性能与响应性
            多线程编程是利用计算机的多核心优势来提高程序的性能和响应性的重要手段之一。在Java中,通过多线程可以实现同时执行多个任务,充分利用CPU资源,加速程序的运行。本文将深入探讨Java多线程编程的基本概念、常用类库、并发问题以及最佳实践。####1.多线程基础概......
  • 总结常用9种下载(限速、多线程加速、ZIP、导Excel)
    一、前言下载文件在我们项目很常见,有下载视频、文件、图片、附件、导出Excel、导出Zip压缩文件等等,这里我对常见的下载做个简单的总结,主要有文件下载、限速下载、多文件打包下载、URL文件打包下载、Excel导出下载、Excel批量导出Zip包下载、多线程加速下载。二、搭建SpringBoo......
  • 在进程中使用条件量和互斥锁实现线程的同步以及临界资源的互斥访问
    /******************************************************************** author :北极甜虾呦* date :2024/06/01* function:进程中使用条件量和互斥锁实现线程的同步以及临界资源的互斥访问* note :None* CopyRight(c)[email protected]......
  • 5种方法,教你判断线程池是否全部完成
    5种方法,教你判断线程池是否全部完成前言isTerminated方式getCompletedTaskCountCountDownLatch计数器CountDownLatch概述维护一个公共计数Future判断任务执行状态前言最近写代码的时候用到了CountDownLatch计数器,然后顺便想了想判断线程池全部结束有多少种方......