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

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

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

  • 新开坑项目: 《Spring AI 项目实战(问答机器人、RAG 增强检索、联网搜索)》 正在持续爆肝中,基于 Spring AI + Spring Boot3.x + JDK 21...点击查看;
  • 《从零手撸:仿小红书(微服务架构)》 已完结,基于 Spring Cloud Alibaba + Spring Boot3.x + JDK 17...点击查看项目介绍; 演示链接: http://116.62.199.48:7070/;
  • 《从零手撸:前后端分离博客项目(全栈开发)》 2 期已完结,演示链接: http://116.62.199.48/

面试考察点

  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 不保证执行。