守护线程
默默在后台服务,用户线程走了,它也得跟着走。
一个故事理解守护线程
想象餐厅里:
- 服务员(用户线程):接待客人、点餐、上菜。客人走光了,服务员也要下班。
- 保洁阿姨(守护线程):收拾桌子、打扫卫生。客人走光了,保洁阿姨也要下班。
- 厨师(用户线程):做菜。客人走光了,厨师也要下班。
但如果你是老板(JVM),你会在所有客人都走了之后,强行让保洁阿姨停下手里的活,而不是等她把桌子擦完。
这就是守护线程的机制:当所有用户线程结束时,JVM 会直接杀死守护线程,不会等它执行完毕。
基本概念
用户线程 vs 守护线程
| 对比项 | 用户线程(User Thread) | 守护线程(Daemon Thread) |
|---|---|---|
| JVM 退出 | 等所有用户线程结束才退出 | JVM 退出时直接杀死 |
| finally 块 | 保证执行 | 可能不执行 |
| 使用场景 | 业务逻辑 | 后台服务 |
JVM 内置的守护线程
你写的代码里没有创建,但 JVM 自动创建的守护线程:
JVM 启动
├── main 用户线程
├── GC 守护线程(垃圾回收)
├── JIT 守护线程(编译优化)
├── Signal Dispatcher 守护线程(信号分发)
└── Reference Handler 守护线程(引用处理)基本用法
设置守护线程
java
Thread daemon = new Thread(() -> {
// 后台任务
while (true) {
System.out.println("守护线程运行中");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {}
}
});
daemon.setDaemon(true); // 必须在 start() 之前设置
daemon.start();必须在 start() 之前设置,否则抛 IllegalThreadStateException:
java
Thread thread = new Thread(() -> {});
thread.setDaemon(true); // ✅ 正常
thread.start(); // ✅ 正常
// vs
Thread thread2 = new Thread(() -> {});
thread2.start();
thread2.setDaemon(true); // ❌ IllegalThreadStateException演示:守护线程的命运
java
public class DaemonDemo {
public static void main(String[] args) throws InterruptedException {
// 用户线程:干 2 秒
Thread userThread = new Thread(() -> {
for (int i = 0; i < 5; i++) {
System.out.println("用户线程: 第 " + (i + 1) + " 秒");
sleep(200);
}
System.out.println("用户线程结束");
}, "UserThread");
// 守护线程:一直跑
Thread daemonThread = new Thread(() -> {
int count = 0;
while (true) {
System.out.println("守护线程: 第 " + (++count) + " 次");
sleep(100);
}
}, "DaemonThread");
daemonThread.setDaemon(true); // 设置为守护线程
daemonThread.start();
userThread.start();
userThread.join();
System.out.println("主线程结束");
// 注意:此时 daemonThread 可能还没执行完
// 但 JVM 退出会直接杀死它
}
private static void sleep(int ms) {
try {
Thread.sleep(ms);
} catch (InterruptedException e) {}
}
}输出(部分):
守护线程: 第 1 次
用户线程: 第 1 秒
守护线程: 第 2 次
用户线程: 第 2 秒
...
用户线程结束
主线程结束可以看到,用户线程结束后,守护线程可能还有「第 N 次」没来得及打印,就被 JVM 杀死了。
守护线程适用场景
适合那些断了也不可惜的后台任务:
1. 监控服务
java
public class MonitoringService {
public static void main(String[] args) {
// 内存监控
Thread memoryMonitor = new Thread(() -> {
Runtime runtime = Runtime.getRuntime();
while (true) {
long used = runtime.totalMemory() - runtime.freeMemory();
System.out.printf("[监控] 内存使用: %.2f MB%n", used / 1024.0 / 1024);
sleep(5000);
}
}, "MemoryMonitor");
memoryMonitor.setDaemon(true);
memoryMonitor.start();
// 业务逻辑
runBusiness();
}
}2. 日志定时刷新
java
// 后台刷新日志缓冲区
Thread logFlusher = new Thread(() -> {
while (true) {
flushLogBuffer(); // 刷新日志
sleep(1000);
}
}, "LogFlusher");
logFlusher.setDaemon(true);
logFlusher.start();3. 心跳检测
java
Thread heartbeat = new Thread(() -> {
while (true) {
sendHeartbeat();
sleep(3000);
}
}, "Heartbeat");
heartbeat.setDaemon(true);
heartbeat.start();4. 定时任务(简单场景)
java
// 简单的定时清理
Thread cleanup = new Thread(() -> {
while (true) {
cleanExpiredCache();
sleep(3600000); // 每小时清理一次
}
}, "CacheCleanup");
cleanup.setDaemon(true);
cleanup.start();守护线程的坑
坑1:finally 可能不执行
java
// ❌ 危险:守护线程在 finally 执行前就被杀死了
Thread daemon = new Thread(() -> {
try {
connection = database.getConnection();
process();
} finally {
connection.close(); // 可能永远不会执行!
}
});
daemon.setDaemon(true);
daemon.start();教训:守护线程不适合执行需要保证完整性的操作(数据库事务、文件写入等)。
坑2:线程池的守护线程问题
java
// ❌ 危险:守护线程池的任务可能跑不完
ExecutorService executor = Executors.newCachedThreadPool();
// 默认创建的线程是用户线程,任务能保证执行
// 如果设置为守护线程
((ThreadPoolExecutor) executor).setThreadFactory(r -> {
Thread t = new Thread(r);
t.setDaemon(true); // 设为守护线程
return t;
});坑3:不适用于关键后台任务
java
// ❌ 不适合:重要的后台处理
Thread transactionProcessor = new Thread(() -> {
while (true) {
processTransaction(); // 重要!
}
});
transactionProcessor.setDaemon(true); // ❌ JVM 退出时任务可能没完成
// ✅ 应该用用户线程或 ScheduledExecutorService
ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
scheduler.scheduleAtFixedRate(() -> processTransaction(), 0, 1, TimeUnit.SECONDS);守护线程 vs ScheduledExecutorService
简单场景用守护线程就够了,但生产环境更推荐 ScheduledExecutorService:
| 对比项 | 守护线程 | ScheduledExecutorService |
|---|---|---|
| 任务保证 | 不保证 | 保证执行 |
| 异常处理 | 简单 | 完善 |
| 灵活性 | 低 | 高 |
| 适用场景 | 非关键后台任务 | 关键定时任务 |
java
// 生产环境推荐:ScheduledExecutorService
ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
// 定时任务
scheduler.scheduleAtFixedRate(() -> {
System.out.println("定时任务执行");
}, 0, 5, TimeUnit.SECONDS);
// 优雅关闭
scheduler.shutdown();
scheduler.awaitTermination(1, TimeUnit.MINUTES);正确用法总结
适合守护线程的场景:
- 监控、统计类任务(数据丢了不心疼)
- 非关键的清理任务
- 心跳、日志刷新等辅助任务
不适合守护线程的场景:
- 数据库操作、文件写入
- 需要事务保证的操作
- 关键业务逻辑
原则:如果任务中断会导致数据丢失或状态不一致,不要用守护线程。
总结
- 守护线程是「默默干活,用户线程走光就下班」的模式
- 必须
start()前设置,否则抛异常 - finally 块可能不执行
- 适合监控、日志刷新等非关键任务
- 关键任务用
ScheduledExecutorService代替守护线程
