⭐⭐⭐ Spring Boot 项目实战 ⭐⭐⭐ Spring Cloud 项目实战
《Dubbo 实现原理与源码解析 —— 精品合集》 《Netty 实现原理与源码解析 —— 精品合集》
《Spring 实现原理与源码解析 —— 精品合集》 《MyBatis 实现原理与源码解析 —— 精品合集》
《Spring MVC 实现原理与源码解析 —— 精品合集》 《数据库实体设计合集》
《Spring Boot 实现原理与源码解析 —— 精品合集》 《Java 面试题 + Java 学习指南》

摘要: 原创出处 http://www.iocoder.cn/Fight/mianshiguanshenmeshishouhuxianchengheputongxianchengyoushenmequbie/ 「芋道源码」欢迎转载,保留摘要,谢谢!


🙂🙂🙂关注微信公众号:【芋道源码】有福利:

  1. RocketMQ / MyCAT / Sharding-JDBC 所有源码分析文章列表
  2. RocketMQ / MyCAT / Sharding-JDBC 中文注释源码 GitHub 地址
  3. 您对于源码的疑问每条留言都将得到认真回复。甚至不知道如何读源码也可以请教噢。
  4. 新的源码解析文章实时收到通知。每周更新一篇左右。
  5. 认真的源码交流微信群。

这道题面试官真正想筛什么

「守护线程是什么 / 和普通线程的区别」——是 JUC 面试的入门题,但能答到加分位的不到 30%。

题面考概念,实际在筛三件事:

  1. 你清不清楚 JVM 退出的判断条件(不是 main 走完,是所有非守护线程结束);
  2. 你有没有用守护线程的实际经验(GC、心跳、监控这些常见场景);
  3. 你有没有踩过守护线程的坑(finally 不保证、子线程继承——这是高分位)。

候选人按答到的深度分三档:30 分(背定义)、60 分(讲清 JVM 退出机制)、90 分(讲三个坑 + ShutdownHook 优雅退出)。下面按段位拆。

L1:30 秒答案过基础线

守护线程(Daemon Thread)是「后台服务线程」。核心区别:所有普通(非守护)线程都结束后,JVM 直接退出,不管守护线程跑到哪一步。

维度 守护线程 普通线程
JVM 退出 不阻止,被强制销毁 阻止,必须全部结束
默认类型 否,要手动 setDaemon(true) 所有线程默认是
设置时机 必须在 start() 之前 不需要

——这是基础线,30 分。能背但不能加分。面试官听完通常会接「那 JVM 是怎么判断要不要退出的?」——这就进入 L2 战场。

L2:2 分钟答案显原理

L2 的关键是讲清 JVM 退出机制 + 举出经典案例。

JVM 退出的唯一判断:所有非守护线程都执行完毕。

注意三个常见误解:

  • 不是 System.exit() 触发——那是显式退出;
  • 不是 main 线程结束触发——main 结束后如果还有用户线程在跑(比如 Spring 容器没关),JVM 不退出;
  • 不看守护线程是否完成——它们和 JVM 一起销毁。

最经典的守护线程是 GC 线程——后台默默回收,所有用户线程结束了,GC 也没必要继续,跟 JVM 一起被销毁。

类比一句话:饭店打烊。客人(普通线程)全走了,服务员(守护线程)不管手头在干什么,关门下班。

能讲清这层逻辑,60 分。

关键深一层的洞察:守护线程不是”低级线程”,它是表达”这个任务是否值得拖住整个 JVM”的语义工具。GC、心跳、监控这种”主流程结束就该停”的事情才适合,业务关键路径绝对不要碰。

L3:5 分钟答案显工程经验

L3 加分位的核心:讲清三个坑 + 给出生产里的优雅退出方案。

坑一:finally 不保证执行

Thread t = new Thread(() -> {
try {
Thread.sleep(5000);
} finally {
System.out.println("资源清理"); // 可能不执行!
}
});
t.setDaemon(true);
t.start();

JVM 退出时守护线程被强制销毁,finally 块来不及跑。

结论:守护线程别做资源释放、数据写入等关键操作——半拉子状态的代价比”没做”还大。

坑二:不适合 IO 操作

写一半文件 JVM 就退了——文件损坏、网络连接半开、消息半发,全是这种半拉子状态。所有涉及外部状态的任务都别用守护线程。

坑三:子线程默认继承父线程的守护属性

Thread daemon = new Thread(() -> {
Thread child = new Thread(...); // 默认也是守护线程!
child.start();
});
daemon.setDaemon(true);
daemon.start();

这个最阴——你以为 new Thread() 是个普通业务线程,结果它继承了父线程的守护属性,跟着父亲一起被强杀。

生产里碰到这个坑,排查方向极不直观——业务线程为什么没跑完就消失了?答:因为它的爸爸是守护线程。

要避免:在守护线程里 new 子线程时显式 setDaemon(false)。

生产里的优雅退出方案:ShutdownHook

需要在 JVM 退出前确保某些清理动作完成?用 ShutdownHook,不要赌守护线程的 finally:

Runtime.getRuntime().addShutdownHook(new Thread(() -> {
log.info("应用正在关闭,执行清理...");
// 关线程池、刷缓冲区、保存状态、断 DB 连接
}));

ShutdownHook 是 JVM 退出前回调的普通线程——只要 JVM 是正常退出(main 结束、System.exit()、收到 SIGTERM 等可控关闭),ShutdownHook 都会被执行;但 Runtime.halt()、SIGKILL、OOM-Killer、宿主机断电、JVM 崩溃这些”硬关”场景一样救不回来。

所以 ShutdownHook 是比守护线程 finally 强一个量级的兜底——但它不是绝对保险。真正可靠的资源一致性还得靠业务层的设计(幂等、补偿、对账、WAL 等)。

为什么这部分最容易拉开分数

讲完三个坑 + ShutdownHook 的候选人,面试官心里已经在打 90 分以上——因为这意味着你不是看了一篇博客背概念,是真在生产里被这些坑教育过。

直接掉分的几种答法

  • 「优先级低的线程」——优先级和守护是两回事,守护和优先级完全无关。
  • 「会被中断的线程」——不是被中断,是 JVM 退出时被强制销毁。
  • 「一定不阻塞主线程的线程」——守护线程也能阻塞、也能耗 CPU,只是不阻止 JVM 退出。
  • 「Spring @Scheduled 用的就是守护线程」——错。@Scheduled 默认普通线程,所以应用不优雅关闭它会一直跑。
  • 「main 是守护线程」——错。main 是普通线程,是顶级用户线程。

高频追问怎么接

追问 1:main 线程是守护线程吗?
答:不是。main 是普通线程。只要 main 还在跑,或者 main 启动的其他普通线程还在跑,JVM 就不退出。

追问 2:守护线程的 finally 一定不执行吗?
答:不一定。如果守护线程是自己正常跑完(比如 run() 方法返回),finally 执行;如果是 JVM 退出导致守护线程被强杀,finally 不保证执行——这是关键区别。

追问 3:Spring Boot 应用怎么处理优雅关闭?
答:Spring Boot 内置 WebServerGracefulShutdown,配置:

server:
shutdown: graceful
spring:
lifecycle:
timeout-per-shutdown-phase: 30s

底层走的是 ShutdownHook 机制——容器关闭时先停止接收新请求,等待已有请求处理完,再关闭线程池、断连接、退出 JVM。

追问 4:线程池里的线程是守护吗?
答:默认不是。Executors.newFixedThreadPool 创建的是普通线程——所以不显式 shutdown() 线程池,JVM 永远退不了。这是经典面试坑:

ExecutorService pool = Executors.newFixedThreadPool(10);
pool.submit(...);
// main 结束,但 JVM 不退出——因为线程池里 10 个普通线程还活着
// 必须显式 pool.shutdown() 才能正常退出

要改成守护线程,得用自定义 ThreadFactory:

ThreadFactory factory = r -> {
Thread t = new Thread(r);
t.setDaemon(true);
return t;
};
ExecutorService pool = Executors.newCachedThreadPool(factory);

追问 5:守护线程能新建普通线程吗?
答:能,但需要显式 setDaemon(false)——默认会继承父线程的守护属性。这是坑三。

一句话收口

守护线程是给普通线程当配角的——主角全走了,配角直接散场。

适合做 GC、心跳、监控这种”可有可无”的后台事,绝不要碰 IO、资源释放、数据持久化——finally 不保证执行。

需要兜底就用 ShutdownHook,不要赌守护线程能跑完。

这题答到 30 分容易(背定义),答到 90 分靠三个坑 + ShutdownHook + 线程池默认是普通线程这个常见陷阱。

文章目录
  1. 1. 这道题面试官真正想筛什么
  2. 2. L1:30 秒答案过基础线
  3. 3. L2:2 分钟答案显原理
  4. 4. L3:5 分钟答案显工程经验
    1. 4.0.1. 坑一:finally 不保证执行
    2. 4.0.2. 坑二:不适合 IO 操作
    3. 4.0.3. 坑三:子线程默认继承父线程的守护属性
    4. 4.0.4. 生产里的优雅退出方案:ShutdownHook
    5. 4.0.5. 为什么这部分最容易拉开分数
  • 5. 直接掉分的几种答法
  • 6. 高频追问怎么接
  • 7. 一句话收口