• java线程池的关闭


    线程池是一个重要的资源,关闭线程池有两种方式:手动和自动。接下来我们一一讲解。

    手动关闭

    线程池有两个方法 shutdown()/shutdownNow()用来关闭,二者的区别:

    • shutdown() 执行后停止接受新任务,会把队列的任务执行完毕。
    • shutdownNow() 也是停止接受新任务,但会中断所有的任务,将线程池状态变为 stop。

    两个方法都会中断线程,用户可自行判断是否需要响应中断。通常,我们可以设置一个shutdownHook来关闭线程池:

    1. Runtime.getRuntime().addShutdownHook(new Thread(() -> {
    2. try {
    3. executorService.shutdown();
    4. while(!executorService.awaitTermination(10,TimeUnit.SECONDS)) {}
    5. } catch(Exception e) {
    6. }
    7. }));

    自动关闭

    首先要了解线程池在什么情况下会自动关闭。ThreadPoolExecutor 类(最常用的线程池实现类)的源码注释中有这么一句话:A pool that is no longer referenced in a program and has no remaining threads will be shutdown automatically.没有引用指向且没有剩余线程的线程池将会自动关闭。

    那么什么情况下线程池中会没有剩余线程呢?先来看一下 ThreadPoolExecutor 参数最全的构造方法:

    1. /**
    2. * @param corePoolSize the number of threads to keep in the pool, even
    3. * if they are idle, unless {@code allowCoreThreadTimeOut} is set
    4. * 核心线程数:即使是空闲状态也可以在线程池存活的线程数量,除非
    5. * allowCoreThreadTimeOut 设置为 true。
    6. * @param keepAliveTime when the number of threads is greater than
    7. * the core, this is the maximum time that excess idle threads
    8. * will wait for new tasks before terminating.
    9. * 存活时间:对于超出核心线程数的线程,空闲时间一旦达到存活时间,就会被销毁。
    10. */
    11. public ThreadPoolExecutor(int corePoolSize,
    12. int maximumPoolSize,
    13. long keepAliveTime,
    14. TimeUnit unit,
    15. BlockingQueue workQueue,
    16. ThreadFactory threadFactory,
    17. RejectedExecutionHandler handler) { ... ... }

    这里只关心与线程存活状态最紧密相关的两个参数:corePoolSize和keepAliveTime。keepAliveTime参数指定了非核心线程的存活时间,非核心线程的空闲时间一旦达到这个值,就会被销毁,而核心线程则会继续存活,只要有线程存活,线程池也就不会自动关闭。聪明的你一定会想到,如果把corePoolSize设置为0,再给keepAliveTime指定一个值的话,那么线程池在空闲一段时间之后,不就可以自动关闭了吗?没错,这就是线程池自动关闭的第一种情况。

    1、自动关闭线程池:核心线程数为 0 并指定线程存活时间

    1. ThreadPoolExecutor executor = new ThreadPoolExecutor(0, 5,
    2. 30L, TimeUnit.SECONDS, new LinkedBlockingQueue<>(15));
    3. for (int i = 0; i < 20; i++) {
    4. executor.execute(() -> {
    5. // 简单地打印当前线程名称
    6. System.out.println(Thread.currentThread().getName());
    7. });
    8. }

    执行上述代码,当线程打印结束后等待30s,程序退出(代表了线程池自动关闭了)。其实Executors.newCachedThrteadPool()创建的线程池,coreP00lSize=0且keepAliveTime=60s,所以也可以自动关闭。其源码如下:

    1. public class Executors {
    2. ... ...
    3. public static ExecutorService newCachedThreadPool() {
    4. return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
    5. 60L, TimeUnit.SECONDS,
    6. new SynchronousQueue());
    7. }
    8. ... ...
    9. }

    如果将上面示例的代码corePoolSize改成大于0的数,当线程打印结束后程序一直不会退出。

    2、自动关闭线程池:通过 allowCoreThreadTimeOut 控制核心线程存活时间

    将核心线程数设置为0虽然可以实现线程池的自动关闭,但也存在一些弊端,根据线程池工作原理,当corePoolSize=0时新到来的任务会永远优先被放入任务队列,然后等待被处理,这显然会影响程序的执行效率。

    那你可能要问了,有没有方法来关闭核心线程呢?答案是肯定的,从 JDK 1.6 开始,ThreadPoolExecutor 类新增了一个allowCoreThreadTimeOut字段,这个字段值默认为false,可使用allowCoreThreadTimeOut()方法对其进行设置,如果设置为 true,那么核心线程数也将受keepAliveTime控制,此方法源码如下:

    1. public void allowCoreThreadTimeOut(boolean value) {
    2. // 核心线程存活时间必须大于0,一旦开启,keepAliveTime 也必须大于0
    3. if (value && keepAliveTime <= 0)
    4. throw new IllegalArgumentException("Core threads must have nonzero keep alive times");
    5. // 将 allowCoreThreadTimeOut 值设为传入的参数值
    6. if (value != allowCoreThreadTimeOut) {
    7. allowCoreThreadTimeOut = value;
    8. // 开启后,清理所有的超时空闲线程,包括核心线程
    9. if (value)
    10. interruptIdleWorkers();
    11. }
    12. }

    接下来,把上面例子修改一下,运行:

    1. ThreadPoolExecutor executor = new ThreadPoolExecutor(1, 5,
    2. 30L, TimeUnit.SECONDS, new LinkedBlockingQueue<>(15));
    3. executor.allowCoreThreadTimeOut(true);
    4. for (int i = 0; i < 20; i++) {
    5. executor.execute(() -> {
    6. // 简单地打印当前线程名称
    7. System.out.println(Thread.currentThread().getName());
    8. });
    9. }

     虽然corePoolSize>0,通过设置allowCoreThreadTimeOut=true,当线程打印结束后等待30s,程序正常退出(说明线程池自动关闭了)

    3、自动关闭线程池:线程池中的线程设置为守护线程

    1. ThreadPoolExecutor executor2 = new ThreadPoolExecutor(1, 5, 30, TimeUnit.SECONDS,
    2. new ArrayBlockingQueue(150),
    3. new ThreadFactory() {
    4. public Thread newThread(Runnable r) {
    5. Thread thread = new Thread(r, r.hashCode()+"");
    6. thread.setDaemon(true);//设置成守护线程
    7. return thread;
    8. }
    9. }
    10. );
    11. for (int i = 0; i < 20; i++) {
    12. executor2.execute(() -> {
    13. // 简单地打印当前线程名称
    14. System.out.println(Thread.currentThread().getName());
    15. });
    16. }

    虽然corePoolSize>0,而且没有设置allowCoreThreadTimeOut,但是在创建线程池时通过ThreadFactory指定了线程为守护线程。当线程打印结束后,无需等待程序正常退出(说明线程池自动关闭了)。当然,这里是在main函数中执行,不存在其他非守护线程哈。

    如何优雅的使用和理解线程池 | crossoverJie's Blog

    java - Java 线程池会自动关闭吗?_个人文章 - SegmentFault 思否

  • 相关阅读:
    目标检测算法——YOLOv5/YOLOv7改进之结合无参注意力SimAM(涨点神器)
    HackTheBox Ambassador 枚举获得用户shell,git consul API提权
    怎么压缩gif图大小,gif压缩到微信表情
    Proxy-Reflect
    CERLAB无人机自主框架: 2-动态目标检测与跟踪
    c语言-浅谈指针(3)
    Gradle介绍2-进阶(Task、插件及部署等)
    java基于springboot+vue的二手车信息网站系统
    力扣刷题-字符串-反转字符串Ⅱ
    debian 添加开机启动项
  • 原文地址:https://blog.csdn.net/liuxiao723846/article/details/127922625