守護線程是 JVM 級別的「後台服務線程」,其核心特性是:當所有用戶線程(非守護線程)結束時,JVM 會自動退出,無視守護線程是否仍在運行。本文通過售票案例演示守護線程行為,並探討線程中斷與優雅關閉的最佳實踐。
thread.setDaemon(true) 必須在 start() 前調用。finally、不釋放鎖、不完成 I/O)。public class TicketDemo {
public static void main(String[] args) throws InterruptedException {
Ticket ticket = new Ticket(); // 共享 100 張票
Thread t1 = new Thread(ticket);
Thread t2 = new Thread(ticket);
t1.setDaemon(true); // 設置為守護線程
t2.setDaemon(true);
t1.start();
t2.start();
// 主線程睡 1 秒後退出
while (true) {
try { Thread.sleep(1000); break; } catch (Exception e) { e.printStackTrace(); }
}
System.out.println("主線程結束,JVM 即將退出");
}
}
運行現象:主線程 1 秒後結束,兩個守護線程可能只賣了幾張票就被強制終止,控制台輸出中斷。若去掉 setDaemon(true),程序會持續運行直到賣完 100 張票。
守護線程的強制終止往往不可接受(如數據未持久化)。正確做法是使用 中斷標誌 + 協作式檢查:
class GracefulTicket implements Runnable {
private int nums = 100;
@Override
public void run() {
while (!Thread.currentThread().isInterrupted() && nums > 0) { // 檢查中斷標誌
synchronized (this) {
if (nums > 0) {
try { Thread.sleep(100); }
catch (InterruptedException e) { // sleep 會拋出 InterruptedException 並清除中斷標誌
Thread.currentThread().interrupt(); // 重新設置標誌
break; // 退出循環
}
System.out.println(Thread.currentThread().getName() + "===" + (nums--));
}
}
}
}
}
調用端:thread.interrupt() 設置標誌,線程在 sleep/wait/join 或檢查 isInterrupted() 時響應退出。
| 特性 | 守護線程 | 用戶線程 + interrupt |
|---|---|---|
| JVM 退出條件 | 僅剩守護線程時強制退出 | 需所有用戶線程正常結束 |
| 資源釋放保障 | 無(finally 不執行) | 有(協作式退出可清理) |
| 適用場景 | 純輔助、可丟失的後台任務 | 業務關鍵、需優雅關閉的服務 |
interrupt() + isInterrupted() 檢查 + InterruptedException 捕獲重置標誌模式。ExecutorService.shutdown() / shutdownNow() 管理線程池生命週期。