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

Java 多線程異常捕獲完全指南:UncaughtExceptionHandler 實戰

     編輯:Java 相關知識

前言:為什麼 try-catch 捕獲不到線程異常?

線程異常不會傳播到主線程。run() 方法簽名不拋出受檢異常,運行時異常拋出後會導致線程終止,異常堆疊輸出到控制台,主線程的 try-catch 完全無感知。這是因為每個線程擁有獨立的調用棧,異常只能在所在棧中捕獲。

一、三種捕獲方案對比

方案適用場景代碼侵入性全局性推薦度
Thread.setDefaultUncaughtExceptionHandler全局兜底、統一日誌上報零侵入✅ JVM 級⭐⭐⭐⭐⭐
ThreadFactory + setUncaughtExceptionHandler線程池隔離、業務分類處理低(工廠封裝)✅ 池級⭐⭐⭐⭐
Future.get() / Callable需要異常結果、併發編排中(改用 Callable)❌ 任務級⭐⭐⭐

二、方案一:全局默認處理器(最推薦)

public class GlobalExceptionHandlerDemo {
    public static void main(String[] args) {
        // 1. 設置全局處理器(盡早設置,main 首行)
        Thread.setDefaultUncaughtExceptionHandler((thread, throwable) -> {
            // 業務邏輯:日誌、告警、上報監控系統
            logError(thread, throwable);
            // 可選:嘗試恢復、重啟關鍵線程
        });

        // 2. 測試:直接 new Thread
        new Thread(() -> {
            System.out.println("子線程運行中...");
            throw new RuntimeException("業務邏輯異常");
        }, "Biz-Thread-1").start();

        // 3. 測試:線程池(繼承默認處理器)
        ExecutorService pool = Executors.newFixedThreadPool(2);
        pool.execute(() -> { throw new NullPointerException("空指針"); });
        pool.shutdown();
    }

    private static void logError(Thread thread, Throwable throwable) {
        System.err.printf("[%s] 線程異常終止: %s%n%s%n", 
            thread.getName(), throwable.getClass().getSimpleName(), 
            Arrays.toString(throwable.getStackTrace()));
        // 實際項目:log.error("", throwable); metrics.counter("thread.exception").increment();
    }
}

三、方案二:自定義 ThreadFactory(線程池隔離)

public class ThreadFactoryExceptionHandler {
    public static void main(String[] args) {
        // 1. 定義業務異常處理器
        Thread.UncaughtExceptionHandler bizHandler = (thread, throwable) -> {
            // 區分業務類型處理
            if (throwable instanceof BizRecoverableException) {
                scheduleRetry((Runnable) throwable.getCause()); // 重試邏輯
            } else {
                alertOnCall(throwable); // 電話/短信告警
            }
        };

        // 2. 自定義 ThreadFactory 綁定處理器
        ThreadFactory factory = new ThreadFactoryBuilder()
            .setNameFormat("Order-Processor-%d")
            .setUncaughtExceptionHandler(bizHandler)
            .setDaemon(false)
            .build();

        // 3. 創建線程池
        ThreadPoolExecutor executor = new ThreadPoolExecutor(
            4, 8, 60L, TimeUnit.SECONDS,
            new ArrayBlockingQueue<>(100),
            factory,
            new ThreadPoolExecutor.CallerRunsPolicy()
        );

        // 4. 提交任務
        IntStream.rangeClosed(1, 10).forEach(i -> 
            executor.execute(() -> {
                if (new Random().nextInt(10) == 0) throw new RuntimeException("隨機故障");
            })
        );
    }
}

四、方案三:Future/Callable 獲取異常結果

public class FutureExceptionHandling {
    public static void main(String[] args) throws Exception {
        ExecutorService executor = Executors.newFixedThreadPool(3);
        
        // 1. 提交 Callable 任務
        Future<String> future = executor.submit(() -> {
            Thread.sleep(100);
            if (System.currentTimeMillis() % 2 == 0) 
                throw new BizException("業務失敗");
            return "成功結果";
        });

        // 2. 獲取結果(阻塞等待,異常包裹在 ExecutionException 中)
        try {
            String result = future.get(5, TimeUnit.SECONDS); // 超時控制
            System.out.println("任務結果: " + result);
        } catch (ExecutionException e) {
            Throwable cause = e.getCause(); // 真實業務異常
            System.err.println("任務異常: " + cause.getMessage());
            // 根據異常類型決策:重試、降級、熔斷
        } catch (TimeoutException e) {
            future.cancel(true); // 中斷執行中的任務
            System.err.println("任務超時已取消");
        } finally {
            executor.shutdown();
        }
    }
}

五、CompletableFuture 異常處理(現代寫法)

CompletableFuture.supplyAsync(() -> {
    if (Math.random() > 0.5) throw new RuntimeException("隨機失敗");
    return "OK";
}, executor)
.exceptionally(ex -> {
    // 異常恢復:返回默認值、降級結果
    log.warn("異常恢復: {}", ex.getMessage());
    return "FALLBACK";
})
.thenApply(result -> result + " -> 處理完成")
.whenComplete((result, ex) -> {
    // 最終回調:無論成敗都執行
    if (ex != null) metrics.error(); else metrics.success();
})
.join();

六、生產環境最佳實踐清單

  1. 啟動類第一行設置全局處理器,確保覆蓋所有非線程池線程
  2. 線程池統一使用自定義 ThreadFactory,綁定業務分級處理器
  3. 異常分類:
    • 可恢復異常:自動重試(指數退避)
    • 業務異常:記錄、告警、人工介入
    • 系統異常(OOM/StackOverflow):快速失敗、觸發熔斷
  4. 上下文保留:MDC 埋點 traceId、spanId,異常日誌自動關聯鏈路
  5. 監控指標:thread.exception.total、thread.exception.by_type、thread.pool.active
  6. 優雅關閉:shutdown() + awaitTermination() + 強制 shutdownNow(),確保異常任務不丟失

七、總結

Java 多線程異常處理核心在於異常不跨棧傳播這一本質認知。建議採用全局兜底 + 線程池隔離 + Future/CompletableFuture 編排三層防線體系。將異常處理從「補丁」升級為「基礎設施」,納入觀測體系,才能構建高可用的併發系統。

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