线程池导致的 shutdown失败的完整排查过程

SpringBoot 中有一种方式可以优雅地关闭应用程序。

(优雅停机是指​关闭应用程序时,在规定的超时时间范围内,允许进行中的请求完成,拒绝新的请求进入​。 这将使应用在请求处理方面保持一致,即没有未处理请求,每一个请求都被处理(完成或拒绝)

配置如下

yml 复制代码
server:
  port: 8888
  shutdown: graceful
management:
  endpoint:
    shutdown:
      enabled: true  
  endpoints:
    web:
      exposure:
        include: shutdown
xml 复制代码
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

现象

直接调用 localhost:8888/actuator/shutdown 即可关闭应用程序。但是在调用某个业务后,再调用 shutdown 的 api,发现实际 shutdown 确实在执行,但是最终并没有把 pid 给 kill 掉,应用程序依然在运行。

第一怀疑就是认为这个程序执行后,还有什么资源没有被关闭掉,导致 springboot 认为应用程序还在运行,从而没有执行关闭操作。

排查过程

执行脚本

shell 复制代码
生成线程快照
jstack -l pid > threads.txt

# 查询非守护进程(因为非守护线程会阻止 JVM 退出) -v 表示反向排除
grep -n '" ' threads2.txt | grep -v daemon
  • 所有线程信息
  • 非守护线程信息

在里面发现了一段 关于 "pool-X-thread-Y"的线程信息,这个 ThreadPoolExecutor 出来的线程,处于等待中,其他的都是额外框架的线程信息或者 jvm 的,只有"pool-X-thread-Y"属于额外的。

less 复制代码
"pool-4-thread-1" #230 prio=5 os_prio=31 cpu=0.21ms elapsed=252.08s tid=0x00000001642cf800 nid=0x9a07 waiting on condition  [0x000000017aaf6000]
   java.lang.Thread.State: WAITING (parking)
	at jdk.internal.misc.Unsafe.park(java.base@17.0.13/Native Method)
	- parking to wait for  <0x0000000701e8af30> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
	at java.util.concurrent.locks.LockSupport.park(java.base@17.0.13/LockSupport.java:341)
	at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionNode.block(java.base@17.0.13/AbstractQueuedSynchronizer.java:506)
	at java.util.concurrent.ForkJoinPool.unmanagedBlock(java.base@17.0.13/ForkJoinPool.java:3465)
	at java.util.concurrent.ForkJoinPool.managedBlock(java.base@17.0.13/ForkJoinPool.java:3436)
	at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(java.base@17.0.13/AbstractQueuedSynchronizer.java:1630)
	at java.util.concurrent.ArrayBlockingQueue.take(java.base@17.0.13/ArrayBlockingQueue.java:420)
	at java.util.concurrent.ThreadPoolExecutor.getTask(java.base@17.0.13/ThreadPoolExecutor.java:1062)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(java.base@17.0.13/ThreadPoolExecutor.java:1122)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(java.base@17.0.13/ThreadPoolExecutor.java:635)
	at java.lang.Thread.run(java.base@17.0.13/Thread.java:840)

   Locked ownable synchronizers:
	- None
  • ThreadPoolExecutor 默认线程名称源码

有了这个排查方向,去项目里面查找关于ThreadPoolExecutor的代码。

最终发现一句关于线程池的声明代码。从代码来看,虽然 XxxConfig 类上加了 @Configuration 注解,受到 spring 管理,但是 XXX_EXECUTOR 这个线程池是静态变量, 并没有受到 spring 管理,所以 springboot 在执行 shutdown 的时候,并不会关闭这个线程池,导致应用程序没有被关闭。

java 复制代码
@Configuration
public class XxxConfig { 
    public static final ThreadPoolExecutor XXX_EXECUTOR = new ThreadPoolExecutor(20, 20, 1000, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<>(10000), new ThreadPoolExecutor.CallerRunsPolicy());

}

最终解决方案

建议将 XXX_EXECUTOR 这个线程池改为 spring 管理的 bean,如下所示:

java 复制代码
@Configuration
public class XxxConfig {
    @Bean("xxxExecutor")
    public ThreadPoolExecutor xxxExecutor() {
        //示例 demo
        return new ThreadPoolExecutor(20, 20, 1000, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<>(10000), new ThreadPoolExecutor.CallerRunsPolicy());
    }
}
复制代码
相关推荐
客卿12311 分钟前
滑动窗口--模板
java·算法
zjjsctcdl34 分钟前
java与mysql连接 使用mysql-connector-java连接msql
java·开发语言·mysql
Moe4881 小时前
WebSocket :从浏览器 API 到 Spring 握手、Handler 与前端客户端
java·后端·架构
顶点多余1 小时前
线程互斥+线程同步+生产消费模型
java·linux·开发语言·c++
⑩-1 小时前
Java基础+集合框架-八股文
java·开发语言
福运常在1 小时前
股票数据API(19)次新股池数据
java·python·maven
Zaki_gd1 小时前
Cortex-M7 D-Cache 与 DMA 缓存一致性说明
java·spring·缓存
多看书少吃饭1 小时前
Vue3 + Java + Python 打造企业级大模型知识库(含 SSE 流式对话完整源码)
java·python·状态模式
Arthas2171 小时前
Java大厂面试:从Spring到微服务的全面技术考察
java·jvm·spring·微服务·面试·并发
mifengxing1 小时前
力扣HOT100——(1)两数之和
java·数据结构·算法·leetcode·hot100