什么是守护线程,和普通线程有什么区别?


一则或许对你有用的小广告

欢迎加入小哈的星球,你将获得:专属的实战项目(4个项目都能学) / 1v1 提问 / 简历修改 / Java 学习路线 / 社群讨论 / 学习打卡 / 每月赠书

  • 《Spring AI 项目实战(问答机器人、RAG 智能客服、联网搜索)》已完结,基于 Spring AI + Spring Boot 3.x + JDK 21...查看介绍

  • 《从零手撸:仿小红书(微服务架构)》 已完结,基于 Spring Cloud Alibaba + Spring Boot 3.x + JDK 17...查看介绍;演示链接:http://116.62.199.48:7070/

  • 《从零手撸:前后端分离博客项目(全栈开发)》 2 期已完结,演示链接:http://116.62.199.48/

  • 新开坑项目:《从零手撸:秒杀系统高并发优化实战》 正在更新中...,查看介绍

截止目前,星球内专栏累计输出 150w+ 字,讲解图 5110+ 张,还在持续爆肝中.. 后续还会上新更多项目,已有 4700+ 小伙伴加入学习,欢迎点击围观

面试考察点

  1. 概念理解:面试官不仅仅是想知道守护线程的定义,更是想考察你是否理解 JVM 退出的条件——当所有非守护线程结束时,不管守护线程有没有跑完,JVM 都会退出。这是理解 Java 线程生命周期的重要一环。

  2. 使用场景认知:能否举出守护线程的实际应用场景(如 GC 线程、监控线程),以及知道哪些场景 不该用 守护线程(涉及 IO、资源操作的任务),体现实践经验。

  3. 踩坑意识:是否清楚守护线程的 "随 JVM 退出" 特性可能导致数据丢失、资源未释放等问题,这是面试中的加分项。

核心答案

守护线程(Daemon Thread) 是一种 "后台服务线程",它的存在是为了给普通线程提供支持。核心区别就一句话:

当所有普通线程(非守护线程)都结束后,JVM 会直接退出,不管守护线程有没有执行完。

对比维度 守护线程 普通线程(用户线程)
JVM 退出行为 不阻止 JVM 退出,随 JVM 一起销毁 阻止 JVM 退出,必须全部结束 JVM 才退出
默认类型 否,需手动设置 所有线程默认都是普通线程
适用场景 GC、监控、日志、后台任务 业务逻辑、数据处理、用户请求
finally 执行 不保证执行,JVM 退出时可能被强行终止 正常执行完毕后 finally 一定会执行
生命周期 依赖普通线程,普通线程没了它也没了 独立,自己跑完才结束

一句话总结:守护线程是 "配角",普通线程是 "主角",主角全走了,配角也就散场了。

深度解析

一、JVM 退出的判断机制

上图展示了 JVM 退出的判断逻辑:

  • JVM 退出的唯一条件:所有 非守护线程(普通线程)都执行完毕。注意,是 JVM 自己的判断,不是你调用 System.exit()
  • 守护线程的命运:不管守护线程有没有执行完、执行到哪一步,只要普通线程全结束了,JVM 就会直接退出,守护线程被 强制销毁
  • 这就好比饭店打烊:客人(普通线程)全走完了,服务员(守护线程)不管手头在干什么,直接关门下班

最经典的守护线程就是 GC 线程:垃圾回收线程是守护线程,它一直在后台默默工作。当所有用户线程都结束了,GC 线程也没必要再运行了,JVM 直接退出。

二、如何创建守护线程

// 方式一:手动设置
Thread t = new Thread(() -> {
    while (true) {
        // 后台监控逻辑,一直循环
    }
});
t.setDaemon(true);  // 必须在 start() 之前调用!
t.start();

// 方式二:通过 ThreadFactory(线程池场景)
ThreadFactory factory = r -> {
    Thread thread = new Thread(r);
    thread.setDaemon(true);  // 线程工厂中统一设置
    return thread;
};
ExecutorService pool = Executors.newCachedThreadPool(factory);

注意事项

  • setDaemon(true) 必须在 start() 之前 调用,否则会抛 IllegalThreadStateException
  • 在线程池中使用时,通过 ThreadFactory 统一设置

三、守护线程的经典使用场景

场景 说明 为什么适合用守护线程
GC 线程 JVM 自带的垃圾回收线程 应用结束了,GC 也没必要继续
心跳检测 定期向注册中心发送心跳 应用都关了,心跳也没意义了
监控采集 定期采集 JVM 指标上报 应用关了就不用采了
日志清理 定期清理过期日志文件 随应用生命周期走
缓存预热/刷新 后台定期刷新本地缓存 应用退出缓存自然失效

四、守护线程的坑(重点)

坑一:finally 不保证执行

Thread t = new Thread(() -> {
    try {
        // 模拟长时间任务
        Thread.sleep(5000);
    } finally {
        // ⚠️ 如果所有普通线程已经结束,JVM 直接退出
        // 这里的代码可能不会执行!
        System.out.println("资源清理");
    }
});
t.setDaemon(true);
t.start();

这意味着守护线程中 不要做资源释放、数据写入等关键操作,因为 JVM 退出时这些操作可能来不及执行。

坑二:不适合 IO 操作

// ❌ 危险!守护线程写文件,可能写到一半 JVM 就退出了
Thread t = new Thread(() -> {
    writeDataToFile(); // 可能写了一半,文件损坏
});
t.setDaemon(true);
t.start();

坑三:守护线程中创建的子线程默认也是守护线程

Thread daemon = new Thread(() -> {
    Thread child = new Thread(() -> {
        // 这个子线程也是守护线程!
        // 因为父线程是守护线程,子线程自动继承
    });
    child.start();
});
daemon.setDaemon(true);
daemon.start();

五、守护线程 vs 普通线程:完整对比

对比维度 守护线程 普通线程
设置方式 setDaemon(true) 默认就是
设置时机 必须 start() 不需要
JVM 退出 不阻止 阻止
finally 执行 不保证 保证
子线程继承 子线程默认也是守护线程 子线程默认也是普通线程
线程优先级 通常设低一点 默认优先级
典型代表 GC 线程、心跳线程 main 线程、业务线程
适合 IO ❌ 不适合 ✅ 适合
适合资源操作 ❌ 不适合 ✅ 适合

面试高频追问

  1. main 线程是守护线程吗?
    • 不是,main 线程是普通线程。只要 main 线程还在执行,或者 main 中启动的其他普通线程还在运行,JVM 就不会退出。
  2. 守护线程的 finally 什么时候会执行?
    • 只有守护线程自己正常执行完毕(或者被中断)时 finally 才会执行。如果是因为所有普通线程结束导致 JVM 退出,finally 中的代码 不保证执行
  3. Spring Boot 应用中怎么用守护线程?
    • Spring 的定时任务(@Scheduled)默认用的就是普通线程,应用需要优雅关闭时才会停。如果你自己手动创建了守护线程做监控,要注意应用关闭时守护线程可能被强杀,需要配合 ShutdownHook 做优雅退出。

常见面试变体

  • "守护线程和用户线程的区别是什么?"
  • "main 方法执行完了,JVM 一定会退出吗?"
  • "什么场景下应该用守护线程?"
  • "为什么 GC 线程要设计成守护线程?"

记忆口诀

定义:守护线程是 "后台配角",普通线程是 "前台主角"

关键规则:主角全走了,配角必须散场(JVM 直接退出)

使用原则:守护线程只做 "可有可无" 的后台事(GC、心跳、监控),涉及数据、IO、资源的任务千万别用

总结

守护线程是为普通线程提供后台服务的线程,当所有普通线程结束后 JVM 会直接退出并强制销毁守护线程。适合用于 GC、心跳、监控等 "可有可无" 的后台任务,不适合涉及 IO、数据写入、资源释放等关键操作,因为 finally 不保证执行。