当前位置: 首页 > news >正文

Spring Boot 线程池关闭:destroyMethod 的作用与最佳实践

一、问题背景

在 Spring Boot 项目中,我们经常会用到线程池来执行后台任务。但如果线程池没有正确关闭,会导致进程无法退出、K8s 滚动更新超时等问题。

先看一段代码:

@Bean(destroyMethod="shutdown")publicLockManagerlockManager(){returnnewLockManager();}

这个destroyMethod = "shutdown"到底做了什么?为什么需要它?

二、destroyMethod 的作用

2.1 Spring Bean 的生命周期

启动 → 实例化 → 依赖注入 → 初始化 → 使用 → 销毁

destroyMethod就是告诉 Spring:容器关闭时,调用这个 Bean 实例上的哪个方法来做资源清理。

2.2 调用链路

Spring 容器关闭 └─ 发现 @Bean(destroyMethod = "shutdown") └─ 反射调用 instance.shutdown() ← 调用的是实例对象的方法 └─ 方法体内:scheduler.shutdownNow() ← 你自己写的清理逻辑

对应代码:

publicclassLockManager{privatefinalScheduledExecutorServicescheduler=Executors.newSingleThreadScheduledExecutor();publicLockManager(){startCleanupTask();// 启动后台清理线程}// Spring 关闭时调用这个方法publicvoidshutdown(){scheduler.shutdownNow();// 关闭线程池}}

2.3 关键区分

名字来源作用
lockManager()@Bean 方法名Bean 的注册名,工厂方法,只调用一次
shutdowndestroyMethod 属性Bean 实例上要调用的销毁方法名

Spring 只负责"找到并调用shutdown()",不关心方法内部做什么。你在shutdown()里写什么,它就执行什么。

三、为什么线程池需要手动关闭

3.1 核心原因:非守护线程

Executors工厂方法创建的线程池,默认都是非守护线程:

// Executors.defaultThreadFactory() 源码publicThreadnewThread(Runnabler){Threadt=newThread(group,r,namePrefix+threadNumber.getAndIncrement(),0);t.setDaemon(false);// ← 写死了非守护t.setPriority(Thread.NORM_PRIORITY);returnt;}
线程类型JVM 行为
守护线程(daemon=true)JVM 不等它,直接退出
非守护线程(daemon=false)JVM 会等它执行完才退出 ← 默认是这个

所有Executors工厂方法默认都是非守护线程:

方法默认 daemon
newSingleThreadScheduledExecutor()false
newScheduledThreadPool(n)false
newFixedThreadPool(n)false
newCachedThreadPool()false
newSingleThreadExecutor()false

3.2 不关闭会怎样

情况一:正常关闭(kill -15 / Ctrl+C)

JVM 收到信号 └─ Spring ShutdownHook 执行,容器关闭 └─ 但 scheduler 线程还在跑(非守护线程) └─ Spring 容器已关闭,但 JVM 还在等这个线程 └─ 结果:进程卡住几秒 → 超时后强制终止

情况二:代码中主动调用 context.close()

main 方法执行完 └─ 但 scheduler 是非守护线程 └─ JVM 不退出,进程挂住,永远不返回

情况三:kill -9

无论配不配destroyMethod,都一样:

  • JVM 直接被杀,什么都不执行

3.3 实际生产影响

影响项严重程度说明
进程无法退出嵌入式/单元测试场景下进程挂死
关闭变慢正常 kill 时多卡几秒
资源泄漏线程未释放,端口/文件句柄未关闭
K8s 滚动更新旧 Pod 关不掉 → 超过 terminationGracePeriodSeconds → SIGKILL
任务执行不完整清理任务可能被中途打断

四、项目中的三种关闭方式

4.1 @Bean(destroyMethod = “shutdown”)

适用于:自定义 Bean 内部持有线程池

@ConfigurationpublicclassAppConfig{@Bean(destroyMethod="shutdown")publicLockManagerlockManager(){returnnewLockManager();}}publicclassLockManager{privatefinalScheduledExecutorServicescheduler=Executors.newSingleThreadScheduledExecutor();publicLockManager(){startCleanupTask();}publicvoidshutdown(){scheduler.shutdownNow();}}

4.2 @PreDestroy 注解

适用于:Spring 组件(@Component)内部持有线程池

@ComponentpublicclassDataToolMethod{privatefinalScheduledExecutorServicescheduler=Executors.newSingleThreadScheduledExecutor();@PostConstructpublicvoidinit(){scheduler.scheduleAtFixedRate(()->{// 定时刷新缓存},1,1,TimeUnit.MINUTES);}@PreDestroypublicvoiddestroy(){if(scheduler!=null&&!scheduler.isShutdown()){scheduler.shutdown();}}}

4.3 finally 手动关闭

适用于:方法级线程池,用完即关

privateList<ReturnShoppingCard>add(...){ExecutorServiceexecutor=null;try{executor=Executors.newSingleThreadExecutor();finalFuture<?>future=executor.submit(()->{// 业务逻辑});future.get(4400,TimeUnit.MILLISECONDS);}catch(Exceptione){future.cancel(true);}finally{executor.shutdown();// 用完即关}returnlist;}

4.4 三种方式对比

方式适用场景优点缺点
destroyMethod自定义 Bean配置简单方法名写死在注解里
@PreDestroySpring 组件语义清晰需要单独写方法
finally方法级线程池即时释放每次创建销毁有开销

五、最佳实践

5.1 优先使用 Spring 管理的线程池

Spring 提供了ThreadPoolTaskExecutor,自带优雅关闭,不需要手动shutdown()

@Configuration@EnableAsyncpublicclassAsyncConfig{@Bean("taskExecutor")publicThreadPoolTaskExecutortaskExecutor(){ThreadPoolTaskExecutorexecutor=newThreadPoolTaskExecutor();executor.setCorePoolSize(4);executor.setMaxPoolSize(8);executor.setQueueCapacity(100);executor.setThreadNamePrefix("task-");executor.setWaitForTasksToCompleteOnShutdown(true);// 等待任务完成executor.setAwaitTerminationSeconds(30);// 最多等30秒executor.initialize();returnexecutor;}}

5.2 如果用原生 ExecutorService,一定要关闭

@ComponentpublicclassMyService{privatefinalScheduledExecutorServicescheduler=Executors.newSingleThreadScheduledExecutor();@PreDestroypublicvoiddestroy(){scheduler.shutdown();// 停止接收新任务try{if(!scheduler.awaitTermination(10,TimeUnit.SECONDS)){scheduler.shutdownNow();// 强制停止}}catch(InterruptedExceptione){scheduler.shutdownNow();Thread.currentThread().interrupt();}}}

5.3 方法级线程池不要每次都 new

// ❌ 不推荐:每次调用都创建销毁线程池publicvoidprocess(){ExecutorServiceexecutor=Executors.newSingleThreadExecutor();try{executor.submit(()->{...});}finally{executor.shutdown();}}// ✅ 推荐:复用类级共享线程池@ServicepublicclassMyService{privatefinalExecutorServiceexecutor=Executors.newSingleThreadExecutor();@PreDestroypublicvoiddestroy(){executor.shutdown();}publicvoidprocess(){executor.submit(()->{...});}}

六、总结

问题答案
destroyMethod 调用的是谁?Bean 实例上的同名方法,不是 @Bean 工厂方法
为什么需要手动关闭线程池?Executors 默认创建非守护线程,会阻止 JVM 退出
不关闭有什么影响?进程挂住、K8s 滚动更新超时、资源泄漏
关闭线程池的三种方式?destroyMethod、@PreDestroy、finally
最佳实践是什么?优先用 Spring 的 ThreadPoolTaskExecutor,自带优雅关闭
http://www.jsqmd.com/news/1294322/

相关文章:

  • 树莓派4B部署YOLOv5模型:OpenVINO与NCS2加速全流程实践
  • 解决C#调用OpenCV时TypeInitializationException的完整指南
  • 2026 武汉中职招生报名时间|省代码 6054 榕霖分三批开学,名额有限 - 湖北升学规划
  • 青岛老旧小区屋面改造,楼长修楼兼顾防水性能与建筑风貌 - 青岛防水品牌推荐
  • A59P专业版:SPI动态调参与拾音距离自适应机制的实现分析
  • 2026 湖北PLC 培训机构哪家好:学费标准|武汉优质自动化工控机构就业率详解 - 学途指南
  • 计算机毕业设计之安卓客户端室内定位APP
  • 二分图算法精讲:从染色法判定到匈牙利算法实战
  • 2026 年武汉统招专升本培训机构怎么挑?全方位深度解读认准初阳教育 - Luckyone王
  • 2026年安徽合肥技师学院网上报名入口在哪?现场报名要带什么?咨询电话多少? - 最新资讯
  • HarmonyOS掌上记账APP开发实践第94篇:如何实现删除多选框选项功能
  • C++与OpenCV实现传统美颜算法:瘦脸、大眼与磨皮技术详解
  • A-59U双通道语音模块:USB免驱架构下的通道隔离与波束性能边界
  • 量子隐形传态:从量子纠缠到量子互联网的核心技术解析
  • 2026年安徽合肥技师学院是公办还是民办?毕业证国家承认吗?咨询电话多少? - 最新资讯
  • Solarflare网卡与OpenOnload部署实战:从驱动安装到性能调优
  • C/C++实现XZ-Ordering空间索引:原理、位交织与高性能优化
  • 山东涡街流量计制造厂家哪些比较好?
  • 2026漠河报团避坑全攻略|5大雷区+5条筛选铁律,实测10家机构,普通人闭眼选不踩雷 - 互联网科技品牌测评
  • 二七区奥迪德系专修宝顺奥迪德系专修申师傅店:车主维保调研解析 - 百航
  • 爱情背后的科学机制:从多巴胺到催产素的神经生物学解析
  • 蒸汽教育如何处理长期服务中的信息断层?
  • PDF-Lib 3层架构设计:构建跨平台JavaScript PDF处理引擎的实现指南
  • PyWxDump:逆向解析微信本地数据库,实现数据自主备份与智能迁移
  • 深圳AI智能体培训机构推荐 - 专业课程与就业前景深度解析
  • 2026 蒙自品质装修口碑榜单|美颂装饰,17 年本土深耕凭个性化设计与管家式服务稳居优选 - 推途云
  • 4步轻松搞定微信防撤回:RevokeMsgPatcher完整使用指南
  • 紧急预警:头条已上线AI内容识别V2.3,未做这3项人工干预的账号本周起限流!
  • 论文科研绘图别硬画❌零基础也能出盲审级图表
  • 2026年瑞之幸商贸:实体商贸与数字化技术融合的典范