1. 线程基础:守护线程 vs 非守护线程
1.1 核心区别
特性 | 守护线程(Daemon Thread) | 非守护线程(Non-Daemon Thread) |
---|---|---|
生命周期 | 随主线程结束而强制终止 | JVM 需等待其执行完毕才会退出 |
用途 | 后台支持任务(如心跳检测、日志) | 核心业务逻辑(如数据库事务) |
默认类型 | 需手动设置 setDaemon(true) | 默认类型 |
资源清理 | 可能被强制终止,需谨慎处理资源 | 通常能正常完成清理逻辑 |
1.2 代码示例
Thread daemonThread = new Thread(() -> {
while (true) {
System.out.println("守护线程运行中...");
try { Thread.sleep(1000); } catch (InterruptedException e) {}
}
});
daemonThread.setDaemon(true); // 设为守护线程
daemonThread.start();
// 主线程结束,守护线程自动终止
2. 守护线程的适用场景与注意事项
2.1 适用场景
✅ 推荐使用守护线程的情况:
- 心跳检测:定期检查连接存活性。
- 日志异步写入:避免阻塞主线程。
ServerSocket.accept()
循环:主线程退出时自动关闭监听。- 缓存刷新:后台定时更新缓存数据。
2.2 注意事项
⚠️ 潜在问题与解决方案:
-
资源泄漏风险
- 守护线程可能被强制终止,导致文件、Socket 未关闭。
- 解决方案:使用
try-finally
确保资源释放:Thread daemonThread = new Thread(() -> { try { while (true) { // 业务逻辑 } } finally { System.out.println("释放资源..."); // 确保执行 } });
-
数据一致性风险
- 守护线程写入数据库时被终止,可能导致数据不完整。
- 解决方案:关键任务使用非守护线程,或实现事务回滚机制。
-
不可靠的定时任务
- 守护线程执行的定时任务可能在任意时刻被终止。
- 解决方案:改用
ScheduledExecutorService
并注册 Shutdown Hook:Runtime.getRuntime().addShutdownHook(new Thread(() -> { executor.shutdownNow(); // 强制停止定时任务 }));
3. 优雅停止线程的 4 种方案
3.1 协作式中断(推荐)
通过 Thread.interrupt()
和 isInterrupted()
实现安全退出:
Thread worker = new Thread(() -> {
while (!Thread.currentThread().isInterrupted()) {
try {
System.out.println("处理任务...");
Thread.sleep(1000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt(); // 重新设置中断标志
break;
}
}
System.out.println("线程优雅退出");
});
worker.start();
// 主线程触发中断
Thread.sleep(3000);
worker.interrupt();
3.2 标志位控制
适用于无法捕获 InterruptedException
的场景:
class Task implements Runnable {
private volatile boolean running = true;
public void stop() { running = false; }
@Override
public void run() {
while (running) {
System.out.println("任务执行中...");
}
}
}
Task task = new Task();
new Thread(task).start();
Thread.sleep(3000);
task.stop(); // 安全停止
3.3 使用 ExecutorService
通过线程池管理生命周期:
ExecutorService executor = Executors.newFixedThreadPool(2);
executor.submit(() -> {
while (!Thread.currentThread().isInterrupted()) {
// 业务逻辑
}
});
// 优雅关闭
executor.shutdown(); // 等待已有任务完成
executor.shutdownNow(); // 立即终止(发送中断信号)
3.4 Java 9 的 Thread.onSpinWait()
适用于高并发场景的自旋等待:
while (!Thread.currentThread().isInterrupted()) {
Thread.onSpinWait(); // 优化自旋性能
// 轻量级任务
}
4. 守护线程的典型应用:Socket 服务端
4.1 Acceptor 线程(守护线程)
Thread acceptor = new Thread(() -> {
try (ServerSocket server = new ServerSocket(8080)) {
while (true) {
Socket client = server.accept();
new Thread(() -> handleClient(client)).start(); // Worker 线程(非守护)
}
} catch (IOException e) { e.printStackTrace(); }
});
acceptor.setDaemon(true); // 主线程退出时自动关闭
acceptor.start();
4.2 Worker 线程(非守护线程)
void handleClient(Socket client) {
try (BufferedReader reader = new BufferedReader(
new InputStreamReader(client.getInputStream()))) {
String line;
while ((line = reader.readLine()) != null) {
System.out.println("客户端消息: " + line);
}
} catch (IOException e) { e.printStackTrace(); }
}
5. 总结与最佳实践
5.1 线程设计原则
- 区分守护与非守护线程:
- 守护线程用于辅助任务,非守护线程用于核心逻辑。
- 避免残留线程:
- 主线程退出前,确保所有非守护线程已完成或中断。
- 资源清理:
- 使用
try-finally
或 Shutdown Hook 释放资源。
- 使用
5.2 优雅停止的最佳实践
- 优先使用
interrupt()
而非stop()
(已废弃)。 - 线程池管理:使用
ExecutorService
替代裸线程。 - 监控线程状态:通过 JMX 或日志跟踪线程生命周期。
最终结论:
守护线程是 Java 多线程编程中的重要工具,但需谨慎使用以避免资源泄漏和数据不一致。优雅停止线程的关键在于协作式中断和合理的生命周期管理。