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虽然可以实现线程池的自动关闭,但也存在一些弊端,此话怎讲,先来看一下线程池的执行流程:

线程池执行流程图

如图所示,当有新的任务到来时,程序会先判断线程池当前线程数是否达到corePoolSize(核心线程数),没达到则创建线程执行任务,达到则尝试将任务放入任务队列 (workQueue)。如果将corePoolSize设置为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. 结语

以上就是线程池可以自动关闭的两种情况,而且梳理了详细的线程池执行流程,相信你看完本文一定会有所收获。不过话又说回来,可自动关闭的线程池的实际应用场景并不多,更多时候需要我们手动关闭。下一篇文章我们就来聊聊如何手动关闭线程池,敬请期待!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,530评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 86,403评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,120评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,770评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,758评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,649评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,021评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,675评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,931评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,659评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,751评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,410评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,004评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,969评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,042评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,493评论 2 343

推荐阅读更多精彩内容