Java 线程池会自动关闭吗|转
在展开描述之前,我们综述一下哪些场景,线程池会自动关闭:
- 没有引用指向且没有剩余线程的线程池
- 核心线程数为0且存活时间大于零的线程池
- Executors.newCachedThrteadPool() 创建的线程池
- 通过 allowCoreThreadTimeOut 设置核心线程可以空闲keepAliveTime的线程池
- 调用shutdown()方法的线程池
接下来详细描述线程池自动关闭的场景。我们来了解线程池在什么情况下会自动关闭。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) { ... ... }
这里我们只关心与线程存活状态最紧密相关的两个参数,也就是corePoolSize和keepAliveTime,上述代码块也包含了这两个参数的源码注释和中文翻译。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() 创建线程池
Executors 是 JDK 自带的线程池框架类,包含多个创建不同类型线程池的方法,而其中的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
Java 线程池会自动关闭吗|转的更多相关文章
- Java 线程池框架核心代码分析--转
原文地址:http://www.codeceo.com/article/java-thread-pool-kernal.html 前言 多线程编程中,为每个任务分配一个线程是不现实的,线程创建的开销和 ...
- Java线程池使用说明
Java线程池使用说明 转自:http://blog.csdn.net/sd0902/article/details/8395677 一简介 线程的使用在java中占有极其重要的地位,在jdk1.4极 ...
- (转载)JAVA线程池管理
平时的开发中线程是个少不了的东西,比如tomcat里的servlet就是线程,没有线程我们如何提供多用户访问呢?不过很多刚开始接触线程的开发攻城师却在这个上面吃了不少苦头.怎么做一套简便的线程开发模式 ...
- Java线程池的那些事
熟悉java多线程的朋友一定十分了解java的线程池,jdk中的核心实现类为java.util.concurrent.ThreadPoolExecutor.大家可能了解到它的原理,甚至看过它的源码:但 ...
- 四种Java线程池用法解析
本文为大家分析四种Java线程池用法,供大家参考,具体内容如下 http://www.jb51.net/article/81843.htm 1.new Thread的弊端 执行一个异步任务你还只是如下 ...
- Java线程池的几种实现 及 常见问题讲解
工作中,经常会涉及到线程.比如有些任务,经常会交与线程去异步执行.抑或服务端程序为每个请求单独建立一个线程处理任务.线程之外的,比如我们用的数据库连接.这些创建销毁或者打开关闭的操作,非常影响系统性能 ...
- Java线程池应用
Executors工具类用于创建Java线程池和定时器. newFixedThreadPool:创建一个可重用固定线程数的线程池,以共享的无界队列方式来运行这些线程.在任意点,在大多数 nThread ...
- Java线程池的原理及几类线程池的介绍
刚刚研究了一下线程池,如果有不足之处,请大家不吝赐教,大家共同学习.共同交流. 在什么情况下使用线程池? 单个任务处理的时间比较短 将需处理的任务的数量大 使用线程池的好处: 减少在创建和销毁线程上所 ...
- Java线程池与java.util.concurrent
Java(Android)线程池 介绍new Thread的弊端及Java四种线程池的使用,对Android同样适用.本文是基础篇,后面会分享下线程池一些高级功能. 1.new Thread的弊端执行 ...
- [转 ]-- Java线程池使用说明
Java线程池使用说明 原文地址:http://blog.csdn.net/sd0902/article/details/8395677 一简介 线程的使用在java中占有极其重要的地位,在jdk1. ...
随机推荐
- pip 提示import error,cannot import name locations
出现这个问题的原因: 环境中没有安装年文件 安装了,环境路径错误 解决如下: 首先 执行升级命令 升级到最新 python -m pip install -U pip 再到site-packages目 ...
- python 代码编写问题
1.解决控制台不输出问题 2.写代码写一些伪代码,即实现过程.步骤 3.再填充代码到伪代码 4.规则 正常变量 不太推荐使用下划线
- Laravel11 从0开发 Swoole-Reverb 扩展包(四) - 触发一个广播事件到reverb服务之后是如何转发给前端订阅的呢(下)?
前情提要 上一篇我们讲到了reverb服务的通信上下文和路由处理,路由实现了pusher关联的几种请求.那么这一篇我们主要来讲混响服务Server 混响 Server 负责基于 ReactPHP 的 ...
- 数据库自增 ID 用完了会怎么样?
前言 数据库中的自增 ID 用完了该怎么办? 这个问题可以分为有主键 & 无主键两种情况回答. 有主键 如果你的表有主键,并且把主键设置为自增. 在 MySQL 中,一般会把主键设置成 int ...
- 基于pandas的数据清洗 -- 重复值的清洗
博客地址:https://www.cnblogs.com/zylyehuo/ 开发环境 anaconda 集成环境:集成好了数据分析和机器学习中所需要的全部环境 安装目录不可以有中文和特殊符号 jup ...
- 团队小规模本地大模型服务平台搭建 - Windows
实现目标和考虑因素 部署一个支持多用户同时使用.多模型运行的离线局域网大模型服务器 需要考虑以下几个关键因素: 大模型的加载和管理.使用一个基础大模型,根据需要创建多个专用模型,模型管理方便可靠. 并 ...
- Pydantic异步校验器深:构建高并发验证系统
title: Pydantic异步校验器深:构建高并发验证系统 date: 2025/3/25 updated: 2025/3/25 author: cmdragon excerpt: Pydanti ...
- Oracle for 循环
Oracle for in loop 循环的一些实例,以作学习和加强使用熟练度及场景应用. 一些技巧 for 语句后面的 loop end loop 可以类比成 c#/java 等编程语言 for 语 ...
- Linux终端居然也可以做文件浏览器?
大家好,我是良许. 在抖音上做直播已经整整 5 个月了,我很自豪我一路坚持到了现在[笑脸] 最近我在做直播的时候,也开始学习鱼皮大佬,直播写代码.当然我不懂 Java 后端,因此就写写自己擅长的 Sh ...
- Fast Prefix Sum Implementation Using Subgroups in GLSL Compute Shaders
利用 Vulkan 1.1 的 subgroup 特性加速 ComputeShader 的前缀和计算,参考: Vulkan Subgroup Tutorial - Khronos Blog - The ...