線程異常不會傳播到主線程。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();
}
}
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("隨機故障");
})
);
}
}
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.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();
thread.exception.total、thread.exception.by_type、thread.pool.activeshutdown() + awaitTermination() + 強制 shutdownNow(),確保異常任務不丟失Java 多線程異常處理核心在於異常不跨棧傳播這一本質認知。建議採用全局兜底 + 線程池隔離 + Future/CompletableFuture 編排三層防線體系。將異常處理從「補丁」升級為「基礎設施」,納入觀測體系,才能構建高可用的併發系統。