1. myuncaughtexceptionhandler 类
这是一个自定义的未捕获异常处理器,当线程因为未捕获异常而终止时,就会调用这个处理器。它会记录错误日志,并且可以添加像通知开发人员等额外的处理逻辑。
package com.hy.archive.config;
import lombok.extern.slf4j.slf4j;
/**
* description: 自定义处理器
* 这是一个自定义的未捕获异常处理器,当线程因为未捕获异常而终止时,就会调用这个处理器。
*
* @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
//记录日志、告警企业微信钉钉通知开发
}
}
使用说明:一般不会直接调用这个类,而是通过线程工厂自动设置到线程上。
2. mythreadfactory 类
这是一个自定义的线程工厂,其作用是对原生线程工厂进行包装。它会为每个新创建的线程设置一个自定义的未捕获异常处理器,也就是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;
}
}
使用说明:在配置线程池时,把这个工厂传递给线程池,这样线程池创建的所有线程都会具备异常捕获能力。
3. threadpoolconfig 类
这是一个 spring 配置类,用于创建和配置一个名为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);
// 线程池优雅停机的关键
executor.setwaitfortaskstocompleteonshutdown(true);
// 线程池前缀配置
executor.setthreadnameprefix("account-exec-");
//满了调用线程执行,认为重要任务
executor.setrejectedexecutionhandler(new threadpoolexecutor.callerrunspolicy());
//设置线程池的异常处理逻辑使用了装饰器设计模式
executor.setthreadfactory(new mythreadfactory(executor));
executor.initialize();
return executor;
}
}
4.举个案例如何使用
1.异步使用
假设你有一个异步服务,使用该线程池执行任务:
@service
public class myasyncservice {
@async("accountexecutor") // 指定使用自定义线程池
public completablefuture<string> processaccount(string accountid) {
// 模拟业务逻辑
if (accountid == null) {
throw new runtimeexception("账户id不能为空"); // 未捕获异常
}
// 正常业务逻辑...
return completablefuture.completedfuture("处理完成");
}
}
当调用 processaccount(null) 时,会触发未捕获异常,此时异常处理流程如下:
- 线程池中的线程执行任务时抛出
runtimeexception。 - 由于线程已通过
mythreadfactory设置了myuncaughtexceptionhandler,异常会被该处理器捕获。 myuncaughtexceptionhandler记录错误日志(例如:exception in thread account-exec-1: 账户id不能为空)。- 可扩展逻辑(如发送告警)被触发。
2.结合 completablefuture
@service
public class accountservice {
@autowired
private threadpooltaskexecutor accountexecutor; // 注入你的线程池
public completablefuture<string> processaccount(string accountid) {
return completablefuture.supplyasync(() -> {
// 模拟业务逻辑,可能抛出异常
if (accountid == null) {
throw new runtimeexception("账户id不能为空");
}
// 正常处理逻辑...
return "账户处理成功: " + accountid;
}, accountexecutor) // 指定使用你的线程池
.exceptionally(ex -> {
// 处理异常的逻辑
log.error("处理账户时发生异常: {}", ex.getmessage(), ex);
return "处理失败: " + ex.getmessage(); // 返回默认值或错误信息
});
}
}
异常处理机制说明
supplyasync+ 指定线程池:
使用 accountexecutor 线程池执行异步任务,该线程池已通过你的 mythreadfactory 设置了 myuncaughtexceptionhandler。
exceptionally回调:
当任务抛出异常时,exceptionally 会捕获异常并返回默认值或处理结果。这是 completablefuture 提供的显式异常处理方式。
双重保障:
- 若未使用
exceptionally,异常会被你的myuncaughtexceptionhandler捕获(记录日志并触发告警)。 - 若使用
exceptionally,异常会被显式处理,同时myuncaughtexceptionhandler仍会记录日志(双重保障)。
验证异常处理效果
@springboottest
class accountservicetest {
@autowired
private accountservice accountservice;
@test
void testexceptionhandling() throws exception {
// 模拟异常情况
completablefuture<string> future = accountservice.processaccount(null);
// 验证异常处理结果
string result = future.get(); // 不会抛出异常,因为exceptionally已处理
assertequals("处理失败: 账户id不能为空", result);
// 检查日志:myuncaughtexceptionhandler也会记录该异常
// error exception in thread account-exec-1: 账户id不能为空
}
}
更复杂的异常处理链
completablefuture 提供了多种异常处理方法,可根据需求组合使用:
public completablefuture<string> processaccount(string accountid) {
return completablefuture.supplyasync(() -> {
if (accountid == null) {
throw new illegalargumentexception("账户id不能为空");
}
if (!isvalid(accountid)) {
throw new runtimeexception("账户id无效");
}
return "账户处理成功: " + accountid;
}, accountexecutor)
.exceptionally(ex -> {
if (ex instanceof illegalargumentexception) {
return "非法参数: " + ex.getmessage();
} else {
return "系统错误: " + ex.getmessage();
}
})
.thenapply(result -> {
// 继续处理结果
return "最终结果: " + result;
})
.exceptionally(ex -> {
// 处理后续步骤可能出现的异常
return "处理失败: " + ex.getmessage();
});
}
关键区别:completablefuture vs 普通异步任务
| 场景 | 异常处理方式 | 是否需要 myuncaughtexceptionhandler |
|---|---|---|
| 普通异步任务 (@async) | 依赖 myuncaughtexceptionhandler 统一处理 | 必须配置 |
| completablefuture + exceptionally | 显式捕获并处理异常 | 可选(用于日志记录和额外监控) |
| completablefuture + 未处理异常 | 依赖 myuncaughtexceptionhandler 捕获 | 必须配置 |
最佳实践建议
优先使用 completablefuture 的显式处理:
在异步任务中,尽量通过 exceptionally()、handle() 或 whencomplete() 显式处理异常,使代码更健壮。
保留统一异常处理器作为兜底:
你的 myuncaughtexceptionhandler 仍有价值,可用于记录所有未被显式处理的异常,避免遗漏问题。
区分可恢复异常和不可恢复异常:
对不同类型的异常采取不同处理策略,例如:
.exceptionally(ex -> {
if (ex instanceof retryableexception) {
// 重试逻辑
return retry(accountid);
} else {
// 记录致命错误并返回默认值
log.error("致命错误", ex);
return "默认结果";
}
})
总结
通过结合你的线程池配置和 completablefuture 的异常处理机制,你可以实现:
- 显式异常处理:通过
exceptionally等方法直接处理已知异常。 - 统一日志记录:利用
myuncaughtexceptionhandler记录所有异常,确保无遗漏。 - 灵活的错误恢复:根据异常类型执行不同的恢复逻辑。
这种组合方式既保证了代码的健壮性,又便于问题追踪和系统监控。
以上为个人经验,希望能给大家一个参考,也希望大家多多支持代码网。
发表评论