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

#Java#面试八股

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

结论先行:守护线程(daemon thread)是不阻止 JVM 退出的线程。JVM 的退出条件只有一个:所有非守护线程都结束了。满足条件时,守护线程会被直接终止——它不保证执行完 finally,也不保证把手上的活干完。

1. 两者的差别

维度用户线程(user thread)守护线程(daemon thread)
是否阻止 JVM 退出是,只要还有一个活着就不退否
默认身份是否,由创建者继承
finally 是否保证执行保证(正常结束或异常)不保证,进程退出即被掐断
典型例子main、业务线程池、Tomcat 的请求线程GC 线程、JIT 编译线程、JMX、日志清理
适合做的事业务逻辑、订单、支付后台辅助:心跳、清理、监控上报

2. JVM 的退出判定

JVM 退出条件:存活的非守护线程数 == 0

main 结束 ──▶ 还有用户线程活着吗?
                 ├── 有 ──▶ 继续运行,JVM 不退出
                 └── 没有 ──▶ 强制终止所有守护线程(finally 不保证执行)──▶ JVM 退出

所以"守护线程"这个词的重点不在"守护",而在"附属":它的生命周期依附于用户线程,用户线程走光了,它也就没有存在的意义。

3. 代码验证

public class DaemonDemo {
    public static void main(String[] args) {
        Thread worker = new Thread(() -> {
            try {
                while (true) {
                    Thread.sleep(100);
                    System.out.println("守护线程还在干活");
                }
            } finally {
                // 大概率不会打印:JVM 退出时不会等它
                System.out.println("finally 执行了吗?");
            }
        }, "daemon-worker");

        worker.setDaemon(true);   // 必须在 start() 之前设置
        worker.start();

        System.out.println("main 结束,JVM 准备退出");
    }
}

运行结果是"main 结束"后进程直接消失,那个 while(true) 并没有拖住 JVM。把 setDaemon(true) 去掉,程序就永远不结束了——这就是两者唯一但决定性的区别。

两个细节:

  • setDaemon() 必须在线程启动前调用,启动后再调会抛 IllegalThreadStateException;
  • 新线程继承创建者的守护标志:在守护线程里 new Thread() 出来的线程默认也是守护线程。这一点最容易被忽略——后台任务里再开线程做业务,可能被"一起带走"。

4. 什么时候该用守护线程

场景是否适合守护线程原因
客户端心跳、定时上报适合主程序退出后无需补发
本地缓存清理适合数据可从源头重建
写数据库、发消息不适合进程退出会丢数据,且没有补偿机会
线程池工作线程视情况自定义 ThreadFactory 里常设 setDaemon(true),避免忘记 shutdown 导致进程不退出
Spring 的 @Async 池一般不适合业务任务要用用户线程,让容器能优雅停机

注意最后一行的实践价值:给线程池的线程设成守护线程,可以让"忘记关闭线程池"不至于阻塞 JVM 退出;但代价是停机时正在执行的任务会被硬砍。生产上更推荐显式调用 shutdown() + awaitTermination(),守护标识只作兜底。

5. 常见追问

追问一:main 线程是守护线程吗? 不是,main 是用户线程。JVM 启动时创建的第一个用户线程就是 main。

追问二:守护线程被终止时,锁会怎样? 线程被强制结束时,它持有的 monitor 会随线程销毁而释放,但临界区里的数据可能处于中间状态。所以绝对不要在守护线程里改共享的关键状态。

追问三:Runtime.addShutdownHook 和守护线程有什么区别? 关闭钩子(shutdown hook)是在 JVM 正常退出流程中执行的用户线程,能保证跑完(除非被 Runtime.halt 或 kill -9 打断);而守护线程是被动终止、毫无保证。需要"退出前收尾"就用钩子,不需要就用守护线程。

追问四:System.exit(0) 会等守护线程吗? 不会。System.exit 触发关闭流程,只等关闭钩子,然后直接终止所有线程。

小结

判断一个线程该不该是守护线程,只问一句:主程序退出时,这个任务丢掉可以接受吗?可以就给守护(后台辅助),不可以就必须用用户线程并做好优雅停机。

笔记加载中…