目录
多线程编程是 Java 的核心能力之一,而在众多线程类型中,用户线程(User Thread) 与 守护线程(Daemon Thread) 是最基础也最容易混淆的两个概念。
一、什么是用户线程?
用户线程(User Thread) 是我们在程序中主动创建的普通线程,执行具体的业务逻辑。
只要有一个用户线程还在运行,JVM 就不会退出。
java
Thread t = new Thread(() -> {
System.out.println("用户线程开始运行...");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
System.out.println("用户线程结束。");
});
t.start();
即使主线程(main)结束,只要这个线程还没执行完,整个进程仍然会保持运行状态。
二、什么是守护线程?
守护线程(Daemon Thread) 是在后台运行、用于服务其他线程的线程。
当所有用户线程结束后,JVM 会立即终止所有守护线程并退出程序。
比如:
- JVM 的垃圾回收(GC)线程;
- 日志异步刷新线程;
- 定时监控或心跳检测线程。
它们的使命是"守护",而不是"主角"。
三、核心区别对比
| 对比项 | 用户线程 | 守护线程 |
|---|---|---|
| 生命周期 | JVM 等待它结束后才退出 | 所有用户线程结束后,JVM 会自动结束 |
| 是否影响 JVM 退出 | ✅ 是 | ❌ 否 |
| 默认类型 | 默认创建为用户线程 | 必须手动设置 |
| 设置方式 | 无需额外操作 | thread.setDaemon(true)(必须在 start() 前调用) |
| 使用场景 | 执行业务逻辑 | 后台维护、服务任务 |
| finally 是否保证执行 | ✅ 通常执行 | ❌ JVM 可能直接退出,来不及执行 |
四、必须记住的规则
setDaemon(true)必须在start()之前调用!
如果在启动线程后再设置,会抛出异常:
java
Thread t = new Thread(() -> {});
t.start();
t.setDaemon(true); // ❌ 抛 IllegalThreadStateException
JVM 在 start() 时就确定了线程是用户线程还是守护线程,之后不能再改。
五、案例一:日志刷新守护线程
在日志系统中,通常会使用一个后台线程定期刷新日志缓冲区。
java
public class DaemonLogFlushDemo {
public static void main(String[] args) throws InterruptedException {
// 守护线程:日志刷新
Thread logFlusher = new Thread(() -> {
try {
while (true) {
System.out.println("[daemon] 刷新日志缓冲区...");
Thread.sleep(500);
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
// JVM 退出时可能不会执行!
System.out.println("[daemon] finally:清理资源");
}
});
logFlusher.setDaemon(true);
logFlusher.start();
// 用户线程:业务任务
Thread user = new Thread(() -> {
try {
for (int i = 1; i <= 3; i++) {
System.out.println("[user ] 处理第 " + i + " 批业务数据");
Thread.sleep(1000);
}
System.out.println("[user ] 业务处理完成");
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
});
user.start();
user.join();
System.out.println("[main ] 所有用户线程结束,JVM 将自动退出");
}
}
- join() 的作用:
让当前线程(main)等待指定线程(user)执行完毕后再继续执行。
运行结果:

守护线程在后台不停打印,一旦用户线程结束,JVM 立即退出。
六、案例二:定时监控守护线程
使用线程池创建守护线程进行定时任务(例如健康检测、心跳监控):
java
import java.util.concurrent.*;
public class DaemonScheduledMonitorDemo {
public static void main(String[] args) throws InterruptedException {
ThreadFactory daemonFactory = r -> {
Thread t = new Thread(r, "monitor-daemon");
t.setDaemon(true);
return t;
};
ScheduledExecutorService scheduler =
Executors.newScheduledThreadPool(1, daemonFactory);
scheduler.scheduleAtFixedRate(
() -> System.out.println("[daemon-scheduler] 心跳监控..."),
0, 300, TimeUnit.MILLISECONDS
);
Thread user = new Thread(() -> {
try {
System.out.println("[user ] 开始执行主任务");
Thread.sleep(2000);
System.out.println("[user ] 主任务完成");
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
});
user.start();
user.join();
System.out.println("[main ] 用户线程结束,JVM 将退出,守护线程随之终止");
}
}

七、使用守护线程的注意事项
-
不要依赖守护线程完成关键任务
守护线程可能在 JVM 退出时被强制中断,
finally块无法保证执行。 -
适用于后台辅助功能
日志、监控、心跳检测等均可使用守护线程。
-
启动顺序要正确
setDaemon(true)必须在start()之前调用,否则抛异常。
八、总结:一句话记住区别
🗝️ 用户线程决定程序生死,守护线程只是陪跑者。
所有用户线程结束后,守护线程将自动随 JVM 一起终止。