首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

Java 守護線程實戰:日誌清理、緩存過期掃描的最佳實踐模式

     編輯:Java 相關知識

守護線程核心特徵

  • 低優先級:僅在無非守護線程運行時獲得 CPU;
  • 不阻塞 JVM 退出:所有非守護線程結束 → JVM 立即關閉,守護線程被強制終止(finally 塊可能不執行);
  • 典型用途:GC、JIT 編譯、日誌刷盤、緩存清理、心跳檢測、監控上報——輔助性、周期性、可丟失的後台任務。

實戰場景:事件隊列生產者 + 守護線程清理器

模擬:3 個生產者線程每秒產生事件,守護線程每輪掃描刪除 10 秒前的過期事件。

1. 事件實體


class Event {
    private final long timestamp = System.currentTimeMillis();
    private final String message;
    Event(String msg) { this.message = msg; }
    long getTimestamp() { return timestamp; }
    String getMessage() { return message; }
}

2. 生產者任務(非守護)


class EventProducer implements Runnable {
    private final Deque queue;
    EventProducer(Deque q) { this.queue = q; }
    @Override public void run() {
        for (int i = 0; i < 100; i++) {
            Event e = new Event("Event-" + Thread.currentThread().getId() + "-" + i);
            queue.addFirst(e); // 頭插,尾部最舊
            try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException ex) { break; }
        }
    }
}

3. 清理器線程(守護)


class EventCleaner extends Thread {
    private final Deque queue;
    EventCleaner(Deque q) { 
        this.queue = q; 
        setDaemon(true); // 關鍵:設置為守護線程
        setName("EventCleaner-Daemon");
    }
    @Override public void run() {
        while (true) { // 無限循環,JVM 退出自動終止
            cleanExpired();
            try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { break; }
        }
    }
    private void cleanExpired() {
        long now = System.currentTimeMillis();
        boolean removed = false;
        // 從尾部(最舊)開始檢查
        while (!queue.isEmpty()) {
            Event oldest = queue.peekLast();
            if (now - oldest.getTimestamp() > 10_000) {
                queue.removeLast();
                System.out.printf("[Cleaner] Removed: %s%n", oldest.getMessage());
                removed = true;
            } else break; // 隊列有序,遇到未過期即可停止
        }
        if (removed) System.out.printf("[Cleaner] Queue size: %d%n", queue.size());
    }
}

4. 主程序啟動


public class DaemonDemo {
    public static void main(String[] args) {
        Deque queue = new ArrayDeque<>();
        // 3 個生產者
        for (int i = 0; i < 3; i++) new Thread(new EventProducer(queue), "Producer-" + i).start();
        // 1 個守護清理器
        new EventCleaner(queue).start();
        // 主線程結束,非守護線程(Producer)還在跑,JVM 不退出
        // 若將 Producer 也設為守護,主線程結束即全部退出
    }
}

運行現象分析

  • 前 10 秒:隊列增長至 ~30,清理器運行但無過期事件;
  • 10 秒後:每秒生產 3、清理 3,隊列大小在 27-30 間波動;
  • 若生產者睡眠縮短(如 200ms),清理器 CPU 分時減少,隊列將無限增長 → 守護線程饑餓。

守護線程使用鐵律

  1. 必須在 start() 前調用 setDaemon(true),啟動後修改拋 IllegalThreadStateException;
  2. 不要在守護線程中執行關鍵業務(如寫 DB、發送關鍵消息),JVM 退出時數據會丟失;
  3. 避免在守護線程中持有鎖,JVM 強制終止不會釋放鎖,導致其他線程死鎖;
  4. 优雅關閉替代方案:使用 Runtime.getRuntime().addShutdownHook() 或 Spring SmartLifecycle,確保非守護線程完成收尾工作。

生產級替代:ScheduledExecutorService


// 替代手寫守護線程,支持優雅關閉、固定頻率/延遲、異常處理
ScheduledExecutorService cleaner = Executors.newSingleThreadScheduledExecutor(r -> {
    Thread t = new Thread(r, "CacheCleaner");
    t.setDaemon(true); // 仍可設置守護
    return t;
});
cleaner.scheduleAtFixedRate(() -> {
    // 掃描清理邏輯
}, 10, 10, TimeUnit.SECONDS);

// 應用關閉時
cleaner.shutdown();
cleaner.awaitTermination(5, TimeUnit.SECONDS);

總結

守護線程是「犧牲自己成全主線程」的利他者。適用於非關鍵、周期性、可重試、無鎖依賴的後台維護任務。生產代碼建議用 ScheduledExecutorService 替代手寫 Thread,獲得標準化生命周期管理與異常處理。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved