核心概念
守护线程(Daemon Thread)是一种特殊的服务线程,为其他线程提供支持服务。当JVM中只剩下守护线程时,JVM会自动退出。
Thread thread = new Thread(() -> {
// 线程逻辑
});
thread.setDaemon(true); // 设置为守护线程
thread.start();
关键特点:
- 守护线程是”后台服务”,不阻止JVM退出
- 用户线程是”主要任务”,会阻止JVM退出
- JVM退出时,守护线程会立即终止(不会执行finally块)
守护线程 vs 用户线程
对比表
| 特性 | 用户线程(User Thread) | 守护线程(Daemon Thread) |
|---|---|---|
| 默认类型 | 是 | 否(需显式设置) |
| JVM退出 | 阻止退出 | 不阻止退出 |
| 创建方式 | new Thread() | setDaemon(true) |
| 父线程影响 | 继承父线程类型 | 继承父线程类型 |
| 典型用途 | 业务逻辑 | 后台服务(GC、监控) |
| finally执行 | ✅ 保证执行 | ❌ 可能不执行 |
示例对比
用户线程(阻止JVM退出)
public class UserThreadDemo {
public static void main(String[] args) {
Thread thread = new Thread(() -> {
for (int i = 1; i <= 5; i++) {
System.out.println("用户线程执行:" + i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
thread.start(); // 默认为用户线程
System.out.println("main线程结束");
}
}
输出:
main线程结束
用户线程执行:1
用户线程执行:2
用户线程执行:3
用户线程执行:4
用户线程执行:5
// 5秒后JVM才退出
结论:main结束后,JVM等待用户线程执行完毕。
守护线程(不阻止JVM退出)
public class DaemonThreadDemo {
public static void main(String[] args) {
Thread thread = new Thread(() -> {
for (int i = 1; i <= 5; i++) {
System.out.println("守护线程执行:" + i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
thread.setDaemon(true); // 设置为守护线程
thread.start();
System.out.println("main线程结束");
}
}
输出:
main线程结束
守护线程执行:1
// JVM立即退出,守护线程被强制终止
结论:main结束后,JVM立即退出,守护线程被中断。
原理分析
1. JVM退出机制
// JVM退出判断逻辑(伪代码)
boolean shouldExit() {
List<Thread> allThreads = getAllThreads();
// 过滤出所有非守护线程
List<Thread> userThreads = allThreads.stream()
.filter(t -> !t.isDaemon())
.collect(Collectors.toList());
// 只剩守护线程时退出
return userThreads.isEmpty();
}
2. 源码分析
设置守护线程
public final void setDaemon(boolean on) {
checkAccess();
if (isAlive()) {
// 必须在start()前调用
throw new IllegalThreadStateException();
}
daemon = on; // 设置守护标志
}
关键点:
- 必须在
start()之前调用 - 启动后无法修改
线程类型继承
// Thread.init()方法片段
private void init(ThreadGroup g, Runnable target, String name,
long stackSize, AccessControlContext acc,
boolean inheritThreadLocals) {
// ...
Thread parent = currentThread();
// 继承父线程的守护属性
this.daemon = parent.isDaemon();
// ...
}
规则:子线程继承父线程的守护属性。
3. 常见守护线程
public class JVMDaemonThreads {
public static void main(String[] args) {
// 获取所有线程
ThreadGroup rootGroup = Thread.currentThread().getThreadGroup();
while (rootGroup.getParent() != null) {
rootGroup = rootGroup.getParent();
}
Thread[] threads = new Thread[rootGroup.activeCount()];
rootGroup.enumerate(threads);
for (Thread thread : threads) {
if (thread != null) {
System.out.printf("%s - Daemon: %b%n",
thread.getName(), thread.isDaemon());
}
}
}
}
典型输出:
main - Daemon: false // 用户线程
Reference Handler - Daemon: true // GC引用处理
Finalizer - Daemon: true // finalize()方法执行
Signal Dispatcher - Daemon: true // 信号分发
Attach Listener - Daemon: true // 附加监听器
应用场景
1. 后台监控服务
public class MonitorService {
private static final Thread monitorThread = new Thread(() -> {
while (true) {
try {
// 监控系统状态
System.out.println("心跳检测:" + LocalDateTime.now());
Thread.sleep(5000);
} catch (InterruptedException e) {
break;
}
}
});
static {
monitorThread.setDaemon(true); // 设置为守护线程
monitorThread.setName("Monitor-Thread");
monitorThread.start();
}
public static void main(String[] args) throws InterruptedException {
System.out.println("主程序开始");
Thread.sleep(12000); // 模拟业务执行
System.out.println("主程序结束");
// JVM退出,监控线程自动结束
}
}
2. 日志异步刷盘
public class AsyncLogger {
private final BlockingQueue<String> logQueue = new LinkedBlockingQueue<>();
public AsyncLogger() {
Thread flushThread = new Thread(() -> {
while (true) {
try {
String log = logQueue.take();
writeToFile(log);
} catch (InterruptedException e) {
break;
}
}
});
flushThread.setDaemon(true); // 守护线程
flushThread.setName("Log-Flush-Thread");
flushThread.start();
}
public void log(String message) {
logQueue.offer(message);
}
private void writeToFile(String log) {
// 写入文件逻辑
}
}
注意:守护线程可能导致日志丢失,生产环境需优雅关闭。
3. 内存清理任务
public class MemoryCleaner {
private static final ScheduledExecutorService scheduler =
Executors.newScheduledThreadPool(1, runnable -> {
Thread thread = new Thread(runnable);
thread.setDaemon(true); // 守护线程
thread.setName("Memory-Cleaner");
return thread;
});
static {
scheduler.scheduleAtFixedRate(() -> {
System.gc(); // 建议GC
System.out.println("执行内存清理");
}, 0, 10, TimeUnit.SECONDS);
}
}
使用陷阱与注意事项
1. finally块可能不执行
public class DaemonFinallyDemo {
public static void main(String[] args) {
Thread thread = new Thread(() -> {
try {
System.out.println("守护线程开始");
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
// ❌ 可能不会执行
System.out.println("finally块执行");
}
});
thread.setDaemon(true);
thread.start();
System.out.println("main结束");
// JVM立即退出,finally可能不执行
}
}
输出:
main结束
守护线程开始
// finally块未执行!
2. 不适合I/O操作
// ❌ 危险:守护线程中执行I/O
Thread ioThread = new Thread(() -> {
try (FileWriter writer = new FileWriter("data.txt")) {
writer.write("重要数据");
writer.flush(); // 可能JVM退出时未完成
} catch (IOException e) {
e.printStackTrace();
}
});
ioThread.setDaemon(true); // 可能导致数据丢失
ioThread.start();
解决方案:使用用户线程 + 优雅关闭。
// ✅ 正确:用户线程 + shutdown钩子
Thread ioThread = new Thread(() -> {
while (!Thread.currentThread().isInterrupted()) {
// I/O操作
}
});
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
ioThread.interrupt();
try {
ioThread.join(5000); // 等待最多5秒
} catch (InterruptedException e) {
e.printStackTrace();
}
}));
ioThread.start();
3. 必须在start()前设置
Thread thread = new Thread(() -> {});
thread.start();
thread.setDaemon(true); // ❌ IllegalThreadStateException
4. 线程池中的守护线程
// 创建守护线程池
ThreadFactory daemonFactory = runnable -> {
Thread thread = new Thread(runnable);
thread.setDaemon(true); // 池中所有线程都是守护线程
return thread;
};
ExecutorService executor = Executors.newFixedThreadPool(10, daemonFactory);
最佳实践
1. 明确线程类型
// ✅ 明确命名和类型
Thread thread = new Thread(() -> {
// 任务逻辑
});
thread.setName("Business-Worker"); // 清晰的名称
thread.setDaemon(false); // 明确指定类型
thread.start();
2. 优雅关闭守护线程
public class GracefulDaemon {
private volatile boolean running = true;
private final Thread daemonThread;
public GracefulDaemon() {
daemonThread = new Thread(() -> {
while (running) {
try {
// 执行任务
Thread.sleep(1000);
} catch (InterruptedException e) {
break;
}
}
// 清理资源
cleanup();
});
daemonThread.setDaemon(true);
daemonThread.start();
}
public void shutdown() {
running = false;
daemonThread.interrupt();
}
private void cleanup() {
System.out.println("清理资源");
}
}
3. 使用shutdown钩子
public class ShutdownHookDemo {
public static void main(String[] args) {
Thread daemonThread = new Thread(() -> {
while (true) {
// 守护任务
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
break;
}
}
});
daemonThread.setDaemon(true);
daemonThread.start();
// 注册关闭钩子
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
System.out.println("JVM即将退出,执行清理");
daemonThread.interrupt();
try {
daemonThread.join(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}));
}
}
答题总结
面试标准答案:
守护线程(Daemon Thread)是一种后台服务线程,与用户线程的核心区别:
定义
- 用户线程:主要任务线程,阻止JVM退出
- 守护线程:后台服务线程,不阻止JVM退出
关键特性
- JVM退出机制:当所有用户线程结束时,JVM立即退出,守护线程被强制终止
- 设置时机:必须在
start()之前调用setDaemon(true) - 类型继承:子线程继承父线程的守护属性
- finally不保证执行:JVM退出时守护线程可能被中断,finally块可能不执行
典型应用
- GC线程:垃圾回收
- 监控线程:系统心跳、性能监控
- 定时任务:缓存清理、日志刷盘
使用注意
- ❌ 不适合重要I/O操作(可能丢失数据)
- ❌ 不适合关键业务逻辑
- ✅ 适合可中断的后台任务
- ✅ 配合shutdown钩子优雅关闭
核心记忆:守护线程 = “配角”,用户线程 = “主角”,主角退场,配角立即下台。