守护线程
守护线程
在 Java 中,线程分为两类:用户线程(User Thread) 和 守护线程(Daemon Thread)。守护线程是一种“后台”线程,它的存在依赖于用户线程:当所有用户线程都结束时,JVM 会自动退出,并强行终止所有仍在运行的守护线程,而不管它们是否执行完毕。
- 为其他线程提供服务:守护线程通常用于执行一些后台任务,如垃圾回收(GC)、JVM 内部监控、心跳检测、日志记录等。
- 不阻止 JVM 退出:只要还有用户线程在运行,JVM 就不会退出;一旦最后一个用户线程结束,JVM 就会停止,所有守护线程也会立即被终止(不会等待它们完成)。
- 生命周期跟随用户线程:守护线程无需手动关闭,会随 JVM 一起消失。
使用 Thread.setDaemon(boolean on) 方法设置守护线程。
Thread daemonThread = new Thread(() -> {
while (true) {
System.out.println("守护线程运行中...");
try { Thread.sleep(1000); } catch (InterruptedException e) {}
}
});
daemonThread.setDaemon(true); // 必须在线程 start() 之前调用
daemonThread.start();
setDaemon(true)必须在start()之前调用,否则会抛出IllegalThreadStateException。- 守护线程中创建的子线程默认也是守护线程(继承父线程的 daemon 状态)。
守护线程 vs 用户线程
| 特性 | 用户线程 | 守护线程 |
|---|---|---|
| JVM 退出条件 | 只要还有用户线程,JVM 就不会退出 | JVM 退出时无条件终止 |
| 典型用途 | 业务逻辑处理 | 后台辅助任务(GC、清理、监控) |
| 是否能依赖 | 可以依赖其正常执行完毕 | 不能依赖其完整执行(可能被突然终止) |
| 设置方式 | 默认就是用户线程 | 需要显式调用 setDaemon(true) |
使用场景与注意事项
✅ 适合使用守护线程的场景
- 垃圾回收(GC):JVM 内置的 GC 线程就是守护线程。
- 自动缓存刷新、过期数据清理:即使被强行终止,下次重启还会重新开始,不会造成数据丢失。
- 监控线程(如监控用户线程是否超时)。
- 心跳发送、日志异步输出:这些操作即使突然中断,通常不会影响核心业务。
❌ 不适合使用守护线程的场景
- 执行关键业务逻辑:例如订单处理、银行转账,如果线程突然被终止,可能导致状态不一致。
- 持有重要资源(文件、数据库连接、网络套接字):守护线程被强制结束时,资源可能无法正确释放(没有 finally 执行机会)。
- 需要执行清理操作(如关闭文件、提交事务):因为 JVM 退出时不会等待守护线程的 finally 块或
shutdown hook。
示例:守护线程中的 finally 不会执行
Thread daemon = new Thread(() -> {
try {
while (true) {
System.out.println("工作");
}
} finally {
System.out.println("finally 不会执行"); // 不会输出
}
});
daemon.setDaemon(true);
daemon.start();
// 主线程结束后,JVM 退出,finally 中的代码永远不会运行
判断线程是否为守护线程
Thread t = new Thread();
System.out.println(t.isDaemon()); // false(默认用户线程)
总结
- 守护线程:用于后台辅助任务,不阻止 JVM 退出,退出时会被强行终止。
- 使用注意:不要在守护线程中执行需要确保完成的操作(如写文件、数据库事务),也不要在里面依赖
finally做资源释放。 - 常见守护线程:JVM 的 GC 线程、Finalizer 线程等。 通过合理使用守护线程,可以让程序在完成主要业务后自动退出,而不必手动关闭所有后台任务。但对于重要的、必须完成的操作,请使用用户线程。