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

Java 並發編程實戰(八):UncaughtExceptionHandler 處理線程非受檢異常

     編輯:Java 相關知識

背景

線程 run() 拋出未捕獲的 非受檢異常(RuntimeException 及子類),默認行為:打印堆棧 → 線程終止 → 若為主線程則 JVM 退出。生產環境需:統一日誌、告警、資源清理、不影響其他線程。

解決方案:Thread.UncaughtExceptionHandler

單線程設置


public class ExceptionHandler implements Thread.UncaughtExceptionHandler {
    private static final Logger log = LoggerFactory.getLogger(ExceptionHandler.class);

    @Override
    public void uncaughtException(Thread t, Throwable e) {
        log.error("線程 [{}] 發生未捕獲異常:{}", t.getName(), e.getMessage(), e);
        // 可在此:上報監控、釋放資源、嘗試重啟任務等
    }
}

// 使用
Thread worker = new Thread(() -> {
    int x = Integer.parseInt("abc"); // NumberFormatException
}, "Parser-Thread");
worker.setUncaughtExceptionHandler(new ExceptionHandler());
worker.start();

全局默認處理器


// 應用啟動早期設置
Thread.setDefaultUncaughtExceptionHandler((t, e) -> {
    log.error("全局捕獲 - 線程:{},異常:{}", t.getName(), e.toString(), e);
});

異常處理器查找順序

  1. 線程實例級:thread.setUncaughtExceptionHandler(handler)
  2. 線程組級:ThreadGroup.uncaughtException()(線程組可繼承重寫)
  3. 全局默認:Thread.setDefaultUncaughtExceptionHandler()
  4. 無處理器 → 默認打印堆棧到 System.err

線程池場景特殊處理

痛點:ExecutorService.execute(Runnable) 提交的任務異常不會觸發上述機制,會被吞噬或包裝在 Future.get() 拋出。

方案一:自定義 ThreadFactory


ThreadFactory factory = r -> {
    Thread t = new Thread(r);
    t.setUncaughtExceptionHandler(new ExceptionHandler());
    return t;
};
ExecutorService pool = Executors.newFixedThreadPool(4, factory);

方案二:包裝 Runnable(通用)


public static Runnable safe(Runnable task, Thread.UncaughtExceptionHandler handler) {
    return () -> {
        try { task.run(); }
        catch (Throwable e) {
            Thread t = Thread.currentThread();
            handler.uncaughtException(t, e);
        }
    };
}

// 使用
pool.execute(safe(() -> { risky(); }, new ExceptionHandler()));

方案三:Future.get() 統一捕獲


Future f = pool.submit(() -> { risky(); });
try { f.get(); }
catch (ExecutionException e) {
    log.error("任務異常", e.getCause());
}

完整示例:帶重試的任務執行器


public class ResilientExecutor {
    private final ExecutorService pool;
    private final Thread.UncaughtExceptionHandler handler;
    private final int maxRetries;

    public ResilientExecutor(int threads, int maxRetries) {
        this.pool = Executors.newFixedThreadPool(threads);
        this.handler = (t, e) -> log.error("任務線程 {} 異常", t.getName(), e);
        this.maxRetries = maxRetries;
    }

    public void submitWithRetry(Runnable task) {
        pool.execute(() -> {
            int attempt = 0;
            while (true) {
                try {
                    task.run();
                    return;
                } catch (Throwable e) {
                    attempt++;
                    if (attempt > maxRetries) {
                        handler.uncaughtException(Thread.currentThread(), e);
                        return;
                    }
                    log.warn("任務失敗,第 {} 次重試", attempt);
                    sleep(1000L * attempt);
                }
            }
        });
    }
    private void sleep(long ms) { try { Thread.sleep(ms); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } }
}

最佳實踐清單

  1. 應用啟動期設置 全局默認處理器,保底收集
  2. 線程池使用 自定義 ThreadFactory 注入處理器
  3. 關鍵業務線程額外設置 實例級處理器,實現差異化處理(告警、降級、重試)
  4. 處理器內 不要拋出新異常,避免遞歸調用
  5. 記錄:線程名、ID、異常類名、消息、完整堆棧、業務上下文 ID
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved