专栏首页Java编程技术使用线程池时候当程序结束时候记得调用shutdown关闭线程池

使用线程池时候当程序结束时候记得调用shutdown关闭线程池

3.10 使用线程池时候当程序结束时候记得调用shutdown关闭线程池

日常开发中为了便于线程的有效复用,线程池是经常会被用的工具,然而线程池使用完后如果不调用shutdown会导致线程池资源一直不会被释放。下面通过简单例子来说明该问题。

3.10.1问题复现

下面通过一个例子说明当不调用线程池对象的shutdown方法后,当线程池里面的任务执行完毕后主线程这个JVM不会退出。

public class TestShutDown {

        static void asynExecuteOne() {
        ExecutorService executor = Executors.newSingleThreadExecutor();
        executor.execute(new  Runnable() {
            public void run() {
                System.out.println("--async execute one ---");
            }
        });
    }
    
    static void asynExecuteTwo() {
        ExecutorService executor = Executors.newSingleThreadExecutor();
        executor.execute(new  Runnable() {
            public void run() {
                System.out.println("--async execute two ---");
            }
        });
    }
    

    public static void main(String[] args) {
       //(1)同步执行
        System.out.println("---sync execute---");
       //(2)异步执行操作one
        asynExecuteOne();
       //(3)异步执行操作two
        asynExecuteTwo();
       //(4)执行完毕
        System.out.println("---execute over---");
    }
}

如上代码主线程里面首先同步执行了操作(1)然后执行操作(2)(3),操作(2)(3)使用线程池的一个线程执行异步操作,我们期望当主线程和操操作(2)(3)执行完线程池里面的任务后整个JVM就会退出,但是执行结果却如下:

image.png

右上角红色方块说明JVM进程还没有退出,Mac上执行ps -eaf|grep java后发现Java进程还是存在的,这是什么情况那?修改操作(2)(3)在方法里面添加调用线程池的shutdown方法如下代码:

            static void asynExecuteOne() {
        ExecutorService executor = Executors.newSingleThreadExecutor();
        executor.execute(new  Runnable() {
            public void run() {
                System.out.println("--async execute one ---");
            }
        });
        
        executor.shutdown();
    }
    
    static void asynExecuteTwo() {
        ExecutorService executor = Executors.newSingleThreadExecutor();
        executor.execute(new  Runnable() {
            public void run() {
                System.out.println("--async execute two ---");
            }
        });
        
        executor.shutdown();
    }

在执行就会发现JVM已经退出了,使用ps -eaf|grep java后发现Java进程以及不存在了,这说明只有调用了线程池的shutdown方法后当线程池任务执行完毕后线程池资源才会释放。

3.10.2问题分析

下面看下为何如此那?大家或许还记得基础篇讲解的守护线程与用户线程吧,JVM退出的条件是当前不存在用户线程,而线程池默认的ThreadFactory创建的线程是用户线程,

    static class DefaultThreadFactory implements ThreadFactory {
        ...
        public Thread newThread(Runnable r) {
            Thread t = new Thread(group, r,
                                  namePrefix + threadNumber.getAndIncrement(),
                                  0);
            if (t.isDaemon())
                t.setDaemon(false);
            if (t.getPriority() != Thread.NORM_PRIORITY)
                t.setPriority(Thread.NORM_PRIORITY);
            return t;
        }
    }

如上代码可知线程池默认的线程工厂创建创建的都是用户线程。而线程池里面的核心线程是一直会存在的,如果没有任务则会阻塞,所以线程池里面的用户线程一直会存在.而shutdown方法的作用就是让这些核心线程终止,下面在简单看下shutdown重要代码:

    public void shutdown() {
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            ...
            //设置线程池状态为SHUTDOWN
            advanceRunState(SHUTDOWN);
            //中断所有的工作线程
            interruptIdleWorkers();
            ...
        } finally {
            mainLock.unlock();
        }
           ...
        }

可知shutdown里面设置了线程池状态为SHUTDOWN,并且设置了所有工作线程的中断标志,那么下面在简单看下工作线程Worker里面是不是发现中断标志被设置了就会退出了。

  final void runWorker(Worker w) {
            ...
            try {
            while (task != null || (task = getTask()) != null) {
               ...            
            }
            ...
          } finally {
            ...
        }
    }
private Runnable getTask() {
        boolean timedOut = false; 

        for (;;) {
            ...
            //(1)
            if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
                decrementWorkerCount();
                return null;
            }
            
            try {
                //(2)
                Runnable r = timed ?
                    workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
                    workQueue.take();
                if (r != null)
                    return r;
                timedOut = true;
            } catch (InterruptedException retry) {
                timedOut = false;
            }
        }
    }

如上代码正常情况下如果队列里面没有任务了,工作线程阻塞到代码(2)等待从工工作队列里面获取一个任务,这时候如果调用了线程池的shutdown命令而shutdown命令会中断所有工作线程,所以代码(2)会抛出处抛出InterruptedException异常而返回,而这个异常被catch了,所以继续执行代码(1),而shutdown时候设置了线程池的状态为SHUTDOWN所以getTask方法返回了null,所以runWorker方法退出循环,该工作线程就退出了。

3.10.3 总结

本节通过一个简单的使用线程池异步执行任务案例介绍了线程池使用完后要如果不调用shutdown会导致线程池的线程资源一直不会被释放,然后通过源码分析了没有被释放的原因。所以日常开发中使用线程池的场景一定不要忘记了调用shutdown方法设置线程池状态和中断工作线程池

--------------------------------相约GitChat探讨技术--------------------------------------

一、常用开源框架 Spring 扩展接口揭秘(文章审核中)

评价一个框架是否优秀,其中必有一点是看该框架是否留足了可扩展的接口。我们在实际做项目或者研发框架时,很多情况下就是在框架留出的扩展接口上进行定制,所以很有必要对这些框架留出了哪些扩展点,这些扩展点是干啥用的有个心知肚明的了解。

本 Chat 将针对 Spring 扩展点进行介绍,主要内容包括:

  • 对 Spring 框架在容器刷新(Refresh 阶段),创建 Bean(getBean),容器销毁(destory)阶段中的扩展接口进行讲解;
  • 对 Spring 中的 ContextLoaderListener 扩展接口进行讲解,并讲解 Webx 框架和 SpringMVC 框架如何使用它,从而让 Tomcat 与应用框架联系起来。

image.png

## 二、SpringBoot核心模块原理分析Chat(文章审核中)

最近微服务很火,SpringBoot 以其轻量级,内嵌 Web 容器,一键启动,方便调试等特点被越来越多的微服务实践者所采用。然而知其然还要知其所以然,本节就来讲解 SpringBoot 的核心模块的实现原理,这些内容在面试的时候也是会被经常问到的:

  • spring-boot-load 模块,正常情况下一个类加载器只能找到加载路径的jar包里面当前目录或者文件类里面的*.class文件,SpringBoot 允许我们使用 java -jar archive.jar 运行包含嵌套依赖 jar 的 jar 或者 war 文件,那么 SpringBoot 是如何实现的那?
  • spring-boot-autoconfigure 模块,Auto-configuration 是 SpringBoot 在 Spring 的基础上提供的一个自动扫描 jar 包里面指定注解的类并注入到 Spring 容器的功能组件。
  • spring-boot 模块,提供了一些特性用来支持 SpringBoot 中其它模块。

三、Java 类加载器揭秘Chat(文章已经出炉)

类加载器作为 JVM 加载字节码到内存中的媒介,其重要性不言而喻,另外在职场面试时候也会被频繁的问道,了解类加载器的原理,能灵活的自定义类加载器去实现自己的功能显得尤为重要。

主要内容:

  • 讲解 Java 中自带的三种类加载器,以及构造原理
  • 讲解类加载器原理
  • 讲解一种特殊的与线程相关类加载器
  • 讲解 Tomcat 框架中多级类加载器的实现原理
  • 讲解如何自定义类加载器实现模块隔离

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • Dubbo线程模型与线程池策略

    Dubbo 默认的底层网络通讯使用的是 Netty ,服务提供方 NettyServer 使用两级线程池,其中 EventLoopGroup(boss) 主要用...

    加多
  • Java中线程池ThreadPoolExecutor原理探究

    线程池主要解决两个问题:一方面当执行大量异步任务时候线程池能够提供较好的性能,,这是因为使用线程池可以使每个任务的调用开销减少(因为线程池线程是可以复用的)。另...

    加多
  • Java 并发编程之美-线程相关的基础知识-chat

    借用 Java 并发编程实践中的话;编写正确的程序并不容易,而编写正常的并发程序就更难了;相比于顺序执行的情况,多线程的线程安全问题是微妙而且出乎意料的,因为在...

    加多
  • Java程序性能基础定位分析

    在做性能测试中不断思考java应用,性能怎么观察,怎么通过方法定位到代码,是否有通用步骤,通过查找资料与参考前人的知识总结,才有如下文章,话说知道不等于会,会不...

    smooth00
  • 美团面试题:Java-线程池 ThreadPool 专题详解

    java.util.concurrent.Executors提供了一个 java.util.concurrent.Executor接口的实现用于创建线程池

    前端博客 : alili.tech
  • Java 多线程编程(“锁”事碎碎念)

    对于多个线程间的共享数据,悲观锁认为自己在使用数据的时候很有可能会有其它线程也刚好前来修改数据,因为在使用数据前都会加上锁,确保在使用过程中数据不会被其它线程修...

    叶志陈
  • Java 多线程编程(聊聊线程池)

    线程是一种昂贵的系统资源,其“昂贵”不仅在于创建线程所需要的资源开销,还在于使用过程中带来的资源消耗。一个系统能够支持同时运行的线程总数受限于该系统所拥有的处理...

    叶志陈
  • 编程体系结构(05):Java多线程并发

    线程是操作系统能够进行运算调度的最小单位,包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程...

    知了一笑
  • 新手一看就懂的线程池

    线程池是帮助我们管理线程的工具,它维护了多个线程,可以降低资源的消耗,提高系统的性能。

    好好学java
  • Java中线程池的参数有几个?

    在使用线程池时,为了获取最佳的性能,常常需要手动指定线程池的参数,ThreadPoolExecutor是最常用的线程池执行器,它有四个构造方法,参数最多的构造方...

    小诸葛

扫码关注云+社区

领取腾讯云代金券