• Java 线程池会自动关闭吗|转


      首先我们需要了解线程池在什么情况下会自动关闭。ThreadPoolExecutor 类(这是我们最常用的线程池实现类)的源码注释中有这么一句话:

    A pool that is no longer referenced in a program and has no remaining threads will be shutdown automatically.

    没有引用指向且没有剩余线程的线程池将会自动关闭。

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

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

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

    1. 核心线程数为 0 并指定线程存活时间

    1.1. 手动创建线程池

    代码示例:

    public class ThreadPoolTest {
        public static void main(String[] args) {
            // 重点关注 corePoolSize 和 keepAliveTime,其他参数不重要
            ThreadPoolExecutor executor = new ThreadPoolExecutor(0, 5,
                    30L, TimeUnit.SECONDS, new LinkedBlockingQueue<>(15));
            for (int i = 0; i < 20; i++) {
                executor.execute(() -> {
                   // 简单地打印当前线程名称
                    System.out.println(Thread.currentThread().getName());
                });
            }
        }
    }
    

    控制台输出结果

    # 线程打印开始
    ... ...
    pool-1-thread-2                    
    pool-1-thread-3
    pool-1-thread-4
    pool-1-thread-5
    pool-1-thread-1
    # 打印结束,程序等待30s后正常退出
    Process finished with exit code 0   # 小知识:exit code 0 说明程序是正常退出,非强行中断或异常退出
    

      通过以上代码和运行结果可以得知,在corePoolSize为0且keepAliveTime设置为 60s 的情况下,如果任务执行完毕又没有新的任务到来,线程池里的线程都将消亡,而且没有核心线程阻止线程池关闭,因此线程池也将随之自动关闭。

      而如果将corePoolSize设置为大于0的数字,再运行以上代码,那么线程池将一直处于等待状态而不能关闭,因为核心线程不受keepAliveTime控制,所以会一直存活,程序也将一直不能结束。运行效果如下 (corePoolSize设置为5,其他参数不变):

    # 线程打印开始
    ... ...
    pool-1-thread-5
    pool-1-thread-1
    pool-1-thread-3
    pool-1-thread-4
    pool-1-thread-2
    # 打印结束,但程序无法结束
    

    2.2 Executors.newCachedThrteadPool() 创建线程池

      ExecutorsJDK 自带的线程池框架类,包含多个创建不同类型线程池的方法,而其中的newCachedThrteadPool()方法也将核心线程数设置为了0并指定了线程存活时间,所以也可以自动关闭。其源码如下:

    public class Executors {
        ... ...
        public static ExecutorService newCachedThreadPool() {
                return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                              60L, TimeUnit.SECONDS,
                                              new SynchronousQueue<Runnable>());
        }
        ... ...
    }
    

      如果用这个线程池运行上面的代码,程序也会自动退出,效果如下

    # 线程打印开始
    ... ...
    pool-1-thread-7
    pool-1-thread-5
    pool-1-thread-4
    pool-1-thread-1
    pool-1-thread-9
    # 打印结束,程序等待60s后退出
    Process finished with exit code 0
    

    2. 通过 allowCoreThreadTimeOut 控制核心线程存活时间

      通过将核心线程数设置为0虽然可以实现线程池的自动关闭,但也存在一些弊端,新到来的任务若发现没有活跃线程,则会优先被放入任务队列,然后等待被处理,这显然会影响程序的执行效率。那你可能要问了,有没有其他的方法来自己实现可自动关闭的线程池呢?答案是肯定的,从 JDK 1.6 开始,ThreadPoolExecutor 类新增了一个allowCoreThreadTimeOut字段:

    /**
     * If false (default), core threads stay alive even when idle.
     * If true, core threads use keepAliveTime to time out waiting
     * for work.
     * 默认为false,核心线程处于空闲状态也可一直存活
     * 如果设置为true,核心线程的存活状态将受keepAliveTime控制,超时将被销毁
     */
    private volatile boolean allowCoreThreadTimeOut;
    

      这个字段值默认为false,可使用allowCoreThreadTimeOut()方法对其进行设置,如果设置为 true,那么核心线程数也将受keepAliveTime控制,此方法源码如下:

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

      既然如此,接下来我们就借助这个方法实现一个可自动关闭且核心线程数不为0的线程池,这里直接在第一个程序的基础上进行改进:

    public class ThreadPoolTest {
        public static void main(String[] args) {
            // 这里把corePoolSize设为5,keepAliveTime保持不变
            ThreadPoolExecutor executor = new ThreadPoolExecutor(5, 5,
                    30L, TimeUnit.SECONDS, new LinkedBlockingQueue<>(15));
            // 允许核心线程超时销毁
            executor.allowCoreThreadTimeOut(true);
            for (int i = 0; i < 20; i++) {
                executor.execute(() -> {
                    System.out.println(Thread.currentThread().getName());
                });
            }
        }
    }
    

      运行结果:

    # 线程打印开始
    ... ...
    pool-1-thread-1
    pool-1-thread-2
    pool-1-thread-3
    pool-1-thread-4
    pool-1-thread-5
    # 打印结束,程序等待30s后退出
    Process finished with exit code 0
    

      可以看到,程序在打印结束后等待了30s,然后自行退出,说明线程池已自动关闭,也就是allowCoreThreadTimeOut()方法发挥了作用。这样,我们就实现了可自动关闭且核心线程数不为0的线程池。

    3. 超详细的线程池执行流程图

      让我们再来梳理一下更完整的线程池执行流程:


    详细的线程池执行流程图

    4. 小结

      以上就是线程池可以自动关闭的两种情况,而且梳理了详细的线程池执行流程,相信你看完本文一定会有所收获。不过话又说回来,可自动关闭的线程池的实际应用场景并不多,更多时候需要我们手动关闭。在执行完任务后调用ExecutorService的shutdown()方法,具体测试用例请参考《Java 自定义线程池的线程工厂》一文中优雅的自定义线程工厂这一节。

    Reference


      读后有收获,小礼物走一走,请作者喝咖啡。

    赞赏支持

  • 相关阅读:
    使用 ant-design/pro-table
    cross-env 根据环境打包
    React 生成图片验证码组件使用
    一些常用的命令行
    react-grid-layout
    vsCode 常用快捷键(mac 版)
    mac 使用命令行,对远程服务器进行文件更新
    原生js 平滑滚动到页面的某个位置
    html2canvas 导出包含滚动条的内容
    react 中的 PureComponent
  • 原文地址:https://www.cnblogs.com/east7/p/15679058.html
Copyright © 2020-2023  润新知