在自定義線程池中捕獲和處理異常實(shí)現(xiàn)過(guò)程
1. MyUncaughtExceptionHandler 類
這是一個(gè)自定義的未捕獲異常處理器,當(dāng)線程因?yàn)槲床东@異常而終止時(shí),就會(huì)調(diào)用這個(gè)處理器。它會(huì)記錄錯(cuò)誤日志,并且可以添加像通知開發(fā)人員等額外的處理邏輯。
package com.hy.archive.config;
import lombok.extern.slf4j.Slf4j;
/**
* Description: 自定義處理器
* 這是一個(gè)自定義的未捕獲異常處理器,當(dāng)線程因?yàn)槲床东@異常而終止時(shí),就會(huì)調(diào)用這個(gè)處理器。
*
* @Author Js
* @Create 2025-07-08 20:04
* @Version 1.0
*/
@Slf4j
public class MyUncaughtExceptionHandler implements Thread.UncaughtExceptionHandler {
@Override
public void uncaughtException(Thread t, Throwable e) {
log.error("Exception in thread :" + e);
// do something
//記錄日志、告警企業(yè)微信釘釘通知開發(fā)
}
}
使用說(shuō)明:一般不會(huì)直接調(diào)用這個(gè)類,而是通過(guò)線程工廠自動(dòng)設(shè)置到線程上。
2. MyThreadFactory 類
這是一個(gè)自定義的線程工廠,其作用是對(duì)原生線程工廠進(jìn)行包裝。它會(huì)為每個(gè)新創(chuàng)建的線程設(shè)置一個(gè)自定義的未捕獲異常處理器,也就是MyUncaughtExceptionHandler。
package com.hy.archive.config;
import java.util.concurrent.ThreadFactory;
/**
* Description: 自定義線程工廠處理
*
* @Author Js
* @Create 2025-07-08 20:01
* @Version 1.0
*/
public class MyThreadFactory implements ThreadFactory {
private final ThreadFactory original;
public MyThreadFactory(ThreadFactory original) {
this.original = original;
}
@Override
public Thread newThread(Runnable r) {
Thread thread = original.newThread(r);
// 異常捕獲
thread.setUncaughtExceptionHandler(new MyUncaughtExceptionHandler());//自定義的未捕獲異常處理器
return thread;
}
}
使用說(shuō)明:在配置線程池時(shí),把這個(gè)工廠傳遞給線程池,這樣線程池創(chuàng)建的所有線程都會(huì)具備異常捕獲能力。
3. ThreadPoolConfig 類
這是一個(gè) Spring 配置類,用于創(chuàng)建和配置一個(gè)名為accountExecutor的線程池。
package com.hy.archive.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.scheduling.annotation.AsyncConfigurer;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import java.util.concurrent.Executor;
import java.util.concurrent.ThreadPoolExecutor;
/**
* 自定義單列線程池配置類
*/
@Configuration
@EnableAsync
public class ThreadPoolConfig implements AsyncConfigurer {
//自定義線程池名稱
public static final String ACCOUNT_EXECUTOR = "accountExecutor";
@Override
public Executor getAsyncExecutor() {
return accountExecutor();
}
@Bean(ACCOUNT_EXECUTOR)
@Primary
public ThreadPoolTaskExecutor accountExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(10);
executor.setMaxPoolSize(20);
executor.setQueueCapacity(500);
// 線程池優(yōu)雅停機(jī)的關(guān)鍵
executor.setWaitForTasksToCompleteOnShutdown(true);
// 線程池前綴配置
executor.setThreadNamePrefix("account-exec-");
//滿了調(diào)用線程執(zhí)行,認(rèn)為重要任務(wù)
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
//設(shè)置線程池的異常處理邏輯使用了裝飾器設(shè)計(jì)模式
executor.setThreadFactory(new MyThreadFactory(executor));
executor.initialize();
return executor;
}
}
4.舉個(gè)案例如何使用
1.異步使用
假設(shè)你有一個(gè)異步服務(wù),使用該線程池執(zhí)行任務(wù):
@Service
public class MyAsyncService {
@Async("accountExecutor") // 指定使用自定義線程池
public CompletableFuture<String> processAccount(String accountId) {
// 模擬業(yè)務(wù)邏輯
if (accountId == null) {
throw new RuntimeException("賬戶ID不能為空"); // 未捕獲異常
}
// 正常業(yè)務(wù)邏輯...
return CompletableFuture.completedFuture("處理完成");
}
}
當(dāng)調(diào)用 processAccount(null) 時(shí),會(huì)觸發(fā)未捕獲異常,此時(shí)異常處理流程如下:
- 線程池中的線程執(zhí)行任務(wù)時(shí)拋出
RuntimeException。 - 由于線程已通過(guò)
MyThreadFactory設(shè)置了MyUncaughtExceptionHandler,異常會(huì)被該處理器捕獲。 MyUncaughtExceptionHandler記錄錯(cuò)誤日志(例如:Exception in thread account-exec-1: 賬戶ID不能為空)。- 可擴(kuò)展邏輯(如發(fā)送告警)被觸發(fā)。
2.結(jié)合 CompletableFuture
@Service
public class AccountService {
@Autowired
private ThreadPoolTaskExecutor accountExecutor; // 注入你的線程池
public CompletableFuture<String> processAccount(String accountId) {
return CompletableFuture.supplyAsync(() -> {
// 模擬業(yè)務(wù)邏輯,可能拋出異常
if (accountId == null) {
throw new RuntimeException("賬戶ID不能為空");
}
// 正常處理邏輯...
return "賬戶處理成功: " + accountId;
}, accountExecutor) // 指定使用你的線程池
.exceptionally(ex -> {
// 處理異常的邏輯
log.error("處理賬戶時(shí)發(fā)生異常: {}", ex.getMessage(), ex);
return "處理失敗: " + ex.getMessage(); // 返回默認(rèn)值或錯(cuò)誤信息
});
}
}
異常處理機(jī)制說(shuō)明
supplyAsync+ 指定線程池:
使用 accountExecutor 線程池執(zhí)行異步任務(wù),該線程池已通過(guò)你的 MyThreadFactory 設(shè)置了 MyUncaughtExceptionHandler。
exceptionally回調(diào):
當(dāng)任務(wù)拋出異常時(shí),exceptionally 會(huì)捕獲異常并返回默認(rèn)值或處理結(jié)果。這是 CompletableFuture 提供的顯式異常處理方式。
雙重保障:
- 若未使用
exceptionally,異常會(huì)被你的MyUncaughtExceptionHandler捕獲(記錄日志并觸發(fā)告警)。 - 若使用
exceptionally,異常會(huì)被顯式處理,同時(shí)MyUncaughtExceptionHandler仍會(huì)記錄日志(雙重保障)。
驗(yàn)證異常處理效果
@SpringBootTest
class AccountServiceTest {
@Autowired
private AccountService accountService;
@Test
void testExceptionHandling() throws Exception {
// 模擬異常情況
CompletableFuture<String> future = accountService.processAccount(null);
// 驗(yàn)證異常處理結(jié)果
String result = future.get(); // 不會(huì)拋出異常,因?yàn)閑xceptionally已處理
assertEquals("處理失敗: 賬戶ID不能為空", result);
// 檢查日志:MyUncaughtExceptionHandler也會(huì)記錄該異常
// ERROR Exception in thread account-exec-1: 賬戶ID不能為空
}
}
更復(fù)雜的異常處理鏈
CompletableFuture 提供了多種異常處理方法,可根據(jù)需求組合使用:
public CompletableFuture<String> processAccount(String accountId) {
return CompletableFuture.supplyAsync(() -> {
if (accountId == null) {
throw new IllegalArgumentException("賬戶ID不能為空");
}
if (!isValid(accountId)) {
throw new RuntimeException("賬戶ID無(wú)效");
}
return "賬戶處理成功: " + accountId;
}, accountExecutor)
.exceptionally(ex -> {
if (ex instanceof IllegalArgumentException) {
return "非法參數(shù): " + ex.getMessage();
} else {
return "系統(tǒng)錯(cuò)誤: " + ex.getMessage();
}
})
.thenApply(result -> {
// 繼續(xù)處理結(jié)果
return "最終結(jié)果: " + result;
})
.exceptionally(ex -> {
// 處理后續(xù)步驟可能出現(xiàn)的異常
return "處理失敗: " + ex.getMessage();
});
}
關(guān)鍵區(qū)別:CompletableFuture vs 普通異步任務(wù)
| 場(chǎng)景 | 異常處理方式 | 是否需要 MyUncaughtExceptionHandler |
|---|---|---|
| 普通異步任務(wù) (@Async) | 依賴 MyUncaughtExceptionHandler 統(tǒng)一處理 | 必須配置 |
| CompletableFuture + exceptionally | 顯式捕獲并處理異常 | 可選(用于日志記錄和額外監(jiān)控) |
| CompletableFuture + 未處理異常 | 依賴 MyUncaughtExceptionHandler 捕獲 | 必須配置 |
最佳實(shí)踐建議
優(yōu)先使用 CompletableFuture 的顯式處理:
在異步任務(wù)中,盡量通過(guò) exceptionally()、handle() 或 whenComplete() 顯式處理異常,使代碼更健壯。
保留統(tǒng)一異常處理器作為兜底:
你的 MyUncaughtExceptionHandler 仍有價(jià)值,可用于記錄所有未被顯式處理的異常,避免遺漏問題。
區(qū)分可恢復(fù)異常和不可恢復(fù)異常:
對(duì)不同類型的異常采取不同處理策略,例如:
.exceptionally(ex -> {
if (ex instanceof RetryableException) {
// 重試邏輯
return retry(accountId);
} else {
// 記錄致命錯(cuò)誤并返回默認(rèn)值
log.error("致命錯(cuò)誤", ex);
return "默認(rèn)結(jié)果";
}
})
總結(jié)
通過(guò)結(jié)合你的線程池配置和 CompletableFuture 的異常處理機(jī)制,你可以實(shí)現(xiàn):
- 顯式異常處理:通過(guò)
exceptionally等方法直接處理已知異常。 - 統(tǒng)一日志記錄:利用
MyUncaughtExceptionHandler記錄所有異常,確保無(wú)遺漏。 - 靈活的錯(cuò)誤恢復(fù):根據(jù)異常類型執(zhí)行不同的恢復(fù)邏輯。
這種組合方式既保證了代碼的健壯性,又便于問題追蹤和系統(tǒng)監(jiān)控。
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
Mybatis之動(dòng)態(tài)SQL使用小結(jié)(全網(wǎng)最新)
MyBatis令人喜歡的一大特性就是動(dòng)態(tài)SQL,?在使用JDBC的過(guò)程中,?根據(jù)條件進(jìn)行SQL的拼接是很麻煩且很容易出錯(cuò)的,MyBatis通過(guò)OGNL來(lái)進(jìn)行動(dòng)態(tài)SQL的使用解決了這個(gè)麻煩,對(duì)Mybatis動(dòng)態(tài)SQL相關(guān)知識(shí)感興趣的朋友跟隨小編一起看看吧2024-05-05
Java字符串技巧之刪除標(biāo)點(diǎn)或最后字符的方法
這篇文章主要介紹了Java字符串技巧之刪除標(biāo)點(diǎn)或最后字符的方法,是Java入門學(xué)習(xí)中的基礎(chǔ)知識(shí),需要的朋友可以參考下2015-11-11
Java toString方法重寫工具之ToStringBuilder案例詳解
這篇文章主要介紹了Java toString方法重寫工具之ToStringBuilder案例詳解,本篇文章通過(guò)簡(jiǎn)要的案例,講解了該項(xiàng)技術(shù)的了解與使用,以下就是詳細(xì)內(nèi)容,需要的朋友可以參考下2021-08-08
springboot項(xiàng)目如何開啟https服務(wù)
這篇文章主要介紹了springboot項(xiàng)目如何開啟https服務(wù)方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2025-04-04
Python安裝Jupyter Notebook配置使用教程詳解
這篇文章主要介紹了Python安裝Jupyter Notebook配置使用教程詳解,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-09-09
SpringBoot啟動(dòng)過(guò)程逐步分析講解
這篇文章主要介紹了SpringBoot啟動(dòng)過(guò)程的實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)吧2023-01-01
Netty源碼分析NioEventLoop初始化線程選擇器創(chuàng)建
這篇文章主要介紹了Netty源碼分析NioEventLoop初始化線程選擇器創(chuàng)建,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-03-03
解決IDEA創(chuàng)建maven項(xiàng)目時(shí)pom.xml沒有變藍(lán)的問題
這篇文章主要介紹了解決IDEA創(chuàng)建maven項(xiàng)目時(shí)pom.xml沒有變藍(lán)的問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-08-08
Java編寫實(shí)現(xiàn)坦克大戰(zhàn)小游戲
這篇文章主要為大家詳細(xì)介紹了Java編寫實(shí)現(xiàn)坦克大戰(zhàn)小游戲,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-01-01

