在 Java 多线程开发中,你可能遇到过一种“奇怪”的现象:
还有子线程还在运行,为什么 JVM 却自动退出了?
这可能是因为你设置了
setDaemon(true) 一、什么是Java的守护线程?
Java的守护线程(Daemon Thread)是指在后台默默运行、为其他线程提供服务的线程。
比如:JVM 的垃圾回收线程(GC Thread)就是典型的守护线程。
Java 线程分为两类:
类型 | 特性 |
用户线程(非守护线程) | 只要任意一个用户线程存活,JVM 就不会退出 |
守护线程 | 当所有用户线程结束,JVM 即使还有守护线程,也会自动退出 |
二、为什么要设置守护线程?
在开发中,我们可能会在自定义线程或线程池中设置
setDaemon(true),目的是让它“自动跟随 JVM 生命周期”。适用场景包括:
1. 自动结束,避免“程序假死”
如果线程是非守护线程,即使主线程结束,程序也可能卡死不退出。守护线程可避免这种情况。
2. 后台维护任务
守护线程适合做一些非核心、后台服务型任务,如:
- 异步日志写入
- 健康监控上报
- 缓存清理或刷新
3. 减少资源泄露风险
守护线程自动结束,不用我们显式关闭,也减少了资源忘记释放的风险。
三、关键示例理解 JVM 的退出机制
示例 1:所有线程都是守护线程,JVM 会立刻退出
现象:
Main ends 输出后,JVM 立即退出。t1 和 t2 虽然在跑,但它们是守护线程,JVM 不会等待它们。示例 2:工作线程中有非守护线程,JVM 等待线程任务结束
现象:
Main ends 输出后,JVM 不会立即退出。t1 线程结束后,JVM 发现只剩守护线程,立即强制退出。示例 3:主线程等待守护线程完成
此方式虽然可以让守护线程执行完,但实质上已经违背了守护线程“随时可结束”的初衷。
四、守护线程的适用场景与使用建议
场景 | 是否适合守护线程 |
异步日志处理 | ✅ 是 |
状态上报、健康检查 | ✅ 是 |
缓存自动刷新任务 | ✅ 是 |
处理业务请求(如 Controller、Feign) | ❌ 否 |
数据库写入、支付接口调用 | ❌ 否 |
tops:
setDaemon(true)必须在start()前调用,否则抛IllegalThreadStateException。
- 守护线程不能用于任何关键业务逻辑,因为它随时可能被 JVM 终止。
五、Spring Cloud 项目中的线程池设置参考
应用场景 | 是否适合设为守护线程 |
@Async、@Scheduled | ❌ 否(业务逻辑) |
RocketMQ/Kafka 消费线程池 | ❌ 否 |
Feign 重试线程池 | ❌ 否 |
日志异步写入、缓存清理 | ✅ 是(后台服务) |
Hystrix/Sentinel 隔离线程池 | ❌ 否(默认非守护) |
一句话总结:只要线程池中执行的是业务逻辑,就不要设为守护线程!
六、深入底层:JVM 如何判断是否“可以退出”
当你调用
thread.setDaemon(true),会影响 JVM 的线程生命周期管理:Java 层 → JVM 层:
JVM 退出方法
destroy_vm() 会等待所有非守护线程都执行完七、JVM 守护线程 ≠ Linux 守护进程(Daemon)
不要混淆 Java 的守护线程与操作系统中的守护进程:
特性 | JVM 守护线程 | Linux 守护进程 |
作用范围 | JVM 内部 | 操作系统层级 |
设定方式 | setDaemon(true) | fork() + setsid() |
生命周期 | 跟 JVM 走 | 通常常驻系统 |
是否脱离控制台 | 否 | 是 |
是否影响 JVM 退出 | 是 | 无关 |
总结:Java守护线程的三条黄金法则
- 别用它做关键任务:因为它可能根本没来得及执行完就被 JVM 干掉。
- 只适用于后台服务:比如日志、缓存、监控等非核心任务。
- 设置顺序要对:务必在
start()之前设置setDaemon(true)。
- 作者:Yibin
- 链接:https://yibin.dev/article/1bf60b50-99a4-803c-b822-f53cb266ac1a
- 声明:本文采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处。
相关文章






