【后端开发】什么是守护线程,和普通线程有什么区别?

文章目录

  • 前言
  • [一、先看现象:JVM 到底什么时候退出?](#一、先看现象:JVM 到底什么时候退出?)
      • [1.1 普通线程会阻止 JVM 退出](#1.1 普通线程会阻止 JVM 退出)
      • [1.2 守护线程不会阻止 JVM 退出](#1.2 守护线程不会阻止 JVM 退出)
    • 二、守护线程为什么存在?
      • [2.1 守护线程的定位:辅助,而不是主业务](#2.1 守护线程的定位:辅助,而不是主业务)
      • [2.2 JVM 自己也在使用守护线程](#2.2 JVM 自己也在使用守护线程)
    • 三、开发中如何正确使用守护线程?
      • [3.1 创建守护线程时,要注意调用时机](#3.1 创建守护线程时,要注意调用时机)
      • [3.2 是否使用守护线程,只看任务是否必须完成](#3.2 是否使用守护线程,只看任务是否必须完成)
      • [3.3 不要把守护线程当成关闭逻辑](#3.3 不要把守护线程当成关闭逻辑)
  • 写在文后

🔥 个人主页:铁皮哥(欢迎关注)

📌 作者简介:28届校招生,后端开发/Agent 方向在学

📚 学习内容:Java、Python、计算机视觉、大语言模型、Agent开发

📝 专栏内容:从零开始的Claude Code零代码生活(持续更新中)

✨不只背八股,更想搞懂为什么这样设计


前言

守护线程并不是一种新的线程实现,它本质上仍然是 Java 线程。它和普通线程真正的区别,不在于能不能执行任务,也不在于执行速度快慢,而在于 JVM 判断程序是否应该退出时,会不会把它算进去。

换句话说,普通线程会影响 JVM 的生命周期,而守护线程不会阻止 JVM 退出。

理解这一点之后,很多现象就变得清晰了。

比如为什么 main 方法结束后,程序不一定马上结束;为什么有些后台线程还没执行完,JVM 却可以直接退出;为什么 GC 线程、监控线程、缓存清理线程这类后台任务,通常适合设计成守护线程。

一、先看现象:JVM 到底什么时候退出?

很多初学者以为 Java 程序结束的标志是 main 方法执行完毕,但实际上并非如此。

JVM 判断程序是否结束,并不依赖主线程是否结束,而是依据是否还存在存活的普通线程。只要还有普通线程在运行,JVM 就认为程序还有工作没完成,因此会继续存活。

1.1 普通线程会阻止 JVM 退出

来看一个简单例子:

java 复制代码
new Thread(() -> {
    while (true) {
        System.out.println("working...");
    }
}).start();

即使 main 方法执行完毕,只要这个线程仍在循环打印日志,JVM 也不会退出。原因在于:

JVM 判断程序结束的标准是存活的普通线程数量,而不是 main 线程是否结束。

换句话说,普通线程决定了 JVM 是否继续活着。

1.2 守护线程不会阻止 JVM 退出

如果将同一个线程改为守护线程:

java 复制代码
Thread thread = new Thread(() -> {
    while (true) {
        System.out.println("monitor...");
    }
});

thread.setDaemon(true);
thread.start();

情况就完全不同了。此时,当所有普通线程结束后,即使守护线程还在运行,JVM 也会直接退出。

由此可以总结一个最核心的结论:

普通线程决定 JVM 是否继续存活,守护线程不会阻止 JVM 退出。

这也是守护线程和普通线程最本质的区别:守护线程的生命周期依附于普通线程,一旦普通线程全部结束,守护线程的存在也随之结束。

二、守护线程为什么存在?

既然守护线程不会阻止 JVM 退出,那 Java 为什么还要设计这种线程?

原因在于,并不是所有线程都代表"程序必须完成的核心任务"。

在一个程序运行过程中,除了真正处理业务的线程之外,还会有很多后台辅助线程。它们不直接完成业务结果,但会在程序运行期间提供支持。守护线程的意义,正是为了表达这种线程的角色定位。

2.1 守护线程的定位:辅助,而不是主业务

守护线程本质上仍然是 Java 线程。

它一样可以执行代码,一样可以参与调度,一样可以在后台运行。它和普通线程的区别,并不是执行能力不同,而是职责不同。

普通线程通常承担的是程序的核心任务。

比如处理用户请求、消费消息、创建订单、写入数据库,这些任务一旦开始执行,通常就应该尽量保证完成。因为它们直接关系到业务结果,如果执行到一半就被中断,很可能会造成数据不一致或者业务异常。

守护线程则更适合承担后台辅助任务。

比如状态监控、缓存清理、心跳检测、资源维护。这些任务在程序运行期间很有价值,但它们本身并不是程序存在的最终目的。程序还在运行时,它们可以持续提供帮助;程序已经结束时,它们也没有必要单独把 JVM 留住。

所以可以这样理解:

普通线程负责把事情做完,守护线程负责在程序活着的时候提供帮助。

这也是"守护"两个字的含义。

它不是程序的主角,而是陪伴程序运行的后台角色。

2.2 JVM 自己也在使用守护线程

其实我们平时写 Java 程序时,即使没有手动创建守护线程,也一直在间接使用它。

最典型的例子就是垃圾回收相关的后台线程。

GC 线程的职责是回收不再使用的对象,帮助 JVM 管理内存。它非常重要,但它的存在是为了服务应用程序运行。只要业务线程还在执行,GC 线程就需要在后台配合工作,避免内存持续膨胀。

但如果所有普通线程都已经结束,说明程序的核心任务已经完成。这个时候,就算 GC 线程还存在,也没有必要为了它继续维持整个 JVM 进程。

这就是守护线程的典型使用场景:

它很重要,但它不决定程序是否继续存在。

类似的后台监控线程、资源清理线程、定时巡检线程,本质上也都符合这个思路。它们适合在程序运行期间默默工作,但不适合成为阻止 JVM 退出的最后一个理由。

因此,守护线程并不是"不重要的线程",而是"不承载最终业务结果的线程"。

理解了这一点,再看守护线程和普通线程的区别,就不会只停留在 setDaemon(true) 这个 API 上,而是能看到背后的设计意图:

JVM 需要区分哪些线程代表核心任务,哪些线程只是为核心任务提供辅助。

三、开发中如何正确使用守护线程?

守护线程并不复杂,真正容易出问题的地方也不是语法,而是使用场景。很多线程相关的问题,往往不是因为不会创建线程,而是把不该放在守护线程里的任务放进去了。

3.1 创建守护线程时,要注意调用时机

创建守护线程的方式很简单,只需要在线程启动之前调用 setDaemon(true)

java 复制代码
Thread thread = new Thread(() -> {
    while (true) {
        System.out.println("后台任务运行中...");
    }
});

thread.setDaemon(true);
thread.start();

这里有一个细节非常重要:

setDaemon(true) 必须在 start() 之前调用。

如果线程已经启动,再去设置它为守护线程,就会抛出 IllegalThreadStateException。因为线程一旦开始运行,它在 JVM 生命周期管理中的身份就已经确定了,不能中途再从普通线程改成守护线程。

3.2 是否使用守护线程,只看任务是否必须完成

实际开发中,判断一个任务能不能放进守护线程,可以用一个很简单的标准:

如果 JVM 退出时,这个任务没有执行完,会不会造成严重后果?

如果答案是会,那它就不应该使用守护线程。

比如订单处理、消息消费、数据落库、文件导出、支付结果通知,这些任务都属于必须完成的业务逻辑。它们一旦开始执行,通常就需要尽量保证结果完整。如果放在守护线程里,JVM 退出时不会等待它们执行结束,就可能出现任务执行到一半就被终止的情况。

这类任务应该交给普通线程,或者交给具备关闭流程的线程池去处理。程序退出时,也应该通过优雅停机的方式等待任务完成,而不是依赖守护线程自动结束。

反过来,如果一个任务只是辅助性的,程序退出时它没有执行完也可以接受,那它就比较适合使用守护线程。

比如状态监控、心跳检测、缓存清理、后台巡检、临时统计,这些任务存在的意义是服务程序运行本身。程序还活着时,它们可以持续提供帮助;程序结束时,它们跟着一起退出,并不会影响核心业务结果。

所以,守护线程适合做"程序活着时顺手做"的事情,不适合做"程序退出前必须完成"的事情。

3.3 不要把守护线程当成关闭逻辑

还有一个常见误区是:有人会把守护线程理解成"程序退出前专门做收尾工作的线程"。

这个理解是错误的。

守护线程不是关闭钩子,也不是退出前的兜底机制。JVM 退出时,并不会保证守护线程里的代码一定执行完。也就是说,如果你把写日志、刷缓存、关闭连接、保存状态这类关键收尾逻辑放进守护线程,就可能出现代码还没执行完,JVM 就已经结束的情况。

如果确实需要在程序退出前做一些收尾操作,应该使用更明确的关闭机制,例如 shutdown hook、线程池关闭流程,或者框架提供的生命周期回调。

写在文后

期待您的一键三连!如果有什么问题或建议欢迎在评论区交流!

相关推荐
ZhengEnCi9 小时前
09bad-斯坦福CS336作业一-构建优化器
人工智能
ZhengEnCi10 小时前
09bac-斯坦福CS336作业一-实现训练损失计算
人工智能
冬奇Lab10 小时前
Skill 系列(01):Skill 评测体系——如何量化一个 AI Skill 的质量
人工智能
兵慌码乱12 小时前
基于 MediaPipe 与 PySide2 的手势交互音乐控制系统实现:轻量化视觉交互全流程解析
python·opencv·计算机视觉·人机交互·手势识别·mediapipe·pyside2
IT_陈寒13 小时前
Redis内存爆了,原来我漏掉了这个致命配置
前端·人工智能·后端
小bo波14 小时前
从"任意文件复制"深挖Java I/O:字符流与字节流的本质抉择
java·nio·io流·后端开发·文件复制
luckdewei15 小时前
FastAPI 资产管理系统实战:复杂 ORM 关联、Alembic 迁移与 N+1 查询优化
python
用户35218024547515 小时前
🎆从 Prompt 到 Skill:让 Spring AI Agent 学会"装新技能"
人工智能·spring boot·ai编程
米小虾15 小时前
手把手教你搭建第一个生产级AI Agent:从选型到实战的完整指南
人工智能·agent
任沫15 小时前
Agent之Function Call
javascript·人工智能·go