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

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

时间:2024-06-03 17:59:24浏览次数:23  
标签:面试官 正在 shutdownNow try 任务 线程 shutdown 执行

优雅的关闭线程池

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

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

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

文章转载自:JavaBuild

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

体验地址:引迈 - JNPF快速开发平台_低代码开发平台_零代码开发平台_流程设计器_表单引擎_工作流引擎_软件架构

标签:面试官,正在,shutdownNow,try,任务,线程,shutdown,执行
From: https://blog.csdn.net/dsgdauigfs/article/details/139414564

相关文章

  • C++实现线程池详解
    在现代软件开发中,高效地管理和利用计算资源是一项关键任务。线程池(ThreadPool)是一种非常有效的并发编程技术,它允许我们管理和重用一组线程,从而避免频繁创建和销毁线程带来的性能开销。1.线程池的基本概念线程池是一组预先创建的线程,这些线程等待并执行任务。当任务到达时,它......
  • Java多线程
    线程的定义Java线程是Java编程语言中的执行单元。在Java中,线程可以看作是轻量级的进程,它独立运行,具有自己的执行路径。线程的原理Java线程的实现基于操作系统的线程模型,但Java虚拟机(JVM)对线程的管理和调度做了封装和优化,使得Java线程更加可控和可靠。下面是Java线程的一些基本......
  • Go高阶16,面试官问我go逃逸场景有哪些,我???
    「逃逸分析」就是程序运行时内存的分配位置(栈或堆),是由编辑器来确定的,而非开发者。什么是栈栈只允许从线性表的同一端放入和取出数据,按照后进先出(LIFO,LastInFirstOut)的顺序,如下图:什么是堆对于堆在内存中的分配,我们可以类比成一个房间,分配内存时,需要找一块足够装下家具......
  • 为师妹写的《Java并发编程之线程池十八问》被表扬啦!
    写在开头  之前给一个大四正在找工作的学妹发了自己总结的关于Java并发中线程池的面试题集,总共18题,将之取名为《Java并发编程之线程池十八问》,今天聊天时受了学妹的夸赞,心里很开心,毕竟自己整理的东西对别人起到了一点帮助,记录一下!Java并发编程之线程池十八问  经......
  • 面试官:说一说如何优雅的关闭线程池,我:shutdownNow,面试官:粗鲁!
    写在开头面试官:“小伙子,线程池使用过吗,来聊一聊它吧!”我:“好的,然后巴拉巴拉一顿输出之前看过的build哥线程池十八问…”面试官满意的点了点头,紧接着问道:“那你知道如何优雅的关闭线程池吗?”我:“知道知道,直接调用shutdownNow()方法就好了呀!”面试官脸色一变,微怒道:“粗......
  • 线程创建的函数及应用小结
    进程是计算机分配资源的基本单位,线程是cpu调度的基本单位线程基本概念:LWP:lightweightprocess轻量级的进程。创建线程的底层函数和进程一样,都是clone,因此线程的本质仍是进程(在linux环境下)与进程相比,线程有独立的TCB结构体(类似于进程的PCB),但没有独立的地址空间(共享),类似于合租......
  • 线程池的实现源码及应用举例
    1.线程池本质​多个线程组成的一个集合,目的为了并发执行任务,定义时是一个结构体,成员有互斥锁,条件变量,任务链队列指针,任务链队列中等待的任务个数,当前活跃的线程数量,线程ID,线程销毁标记等2.线程池的关键技术(1)万能函数指针(通用函数指针):*void*(*p)(voi......
  • Java面试题:解释一下Java中的synchronized关键字,它是如何保证线程安全的?
    在Java中,synchronized关键字是一种同步锁机制,用于确保多个线程在访问共享资源时能够保持线程安全。线程安全是指在多线程环境下,当多个线程尝试同时访问共享资源时,任何时刻最多只有一个线程能够执行特定的代码段。synchronized关键字可以用于以下几个方面:方法同步:当synch......
  • Redis单线程
    Redis是基于Reactor模式开发的网络事件处理器,这个处理器是单线程的,所以redis是单线程的。为什么它是单线程还那么快呢?主要有以下几个原因:一、纯内存操作由于Redis是纯内存操作,相比于磁盘来说,内存就快得多,这个是Redis快的主要原因。二、多路复用I/O机制(NIO)Re......
  • 信号量(Semaphore),事件Event(了解),队列补充,进程池和线程池(重点),协程理论,Greenlet,Gevent模
    Ⅰ信号量(Semaphore)【一】什么是信号量信号量Semahpore(同线程一样)互斥锁:允许在同一时刻只能有一个线程或进程同资源进行修改信号量:允许指定数量的进程或线程对资源进行修改【二】例子比如厕所有3个坑,那最多只允许3个人上厕所,后面的人只能等里面有人出来了才能再进去......