在自定义线程池中捕获和处理异常实现过程

 更新时间:2026年05月14日 09:59:13   作者:北执南念  
这篇文章主要介绍了在自定义线程池中捕获和处理异常实现过程,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教

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 记录所有异常,确保无遗漏。
  • 灵活的错误恢复:根据异常类型执行不同的恢复逻辑。

这种组合方式既保证了代码的健壮性,又便于问题追踪和系统监控。

以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。

相关文章

  • Mybatis之动态SQL使用小结(全网最新)

    Mybatis之动态SQL使用小结(全网最新)

    MyBatis令人喜欢的一大特性就是动态SQL, 在使用JDBC的过程中, 根据条件进行SQL的拼接是很麻烦且很容易出错的,MyBatis通过OGNL来进行动态SQL的使用解决了这个麻烦,对Mybatis动态SQL相关知识感兴趣的朋友跟随小编一起看看吧
    2024-05-05
  • Java字符串技巧之删除标点或最后字符的方法

    Java字符串技巧之删除标点或最后字符的方法

    这篇文章主要介绍了Java字符串技巧之删除标点或最后字符的方法,是Java入门学习中的基础知识,需要的朋友可以参考下
    2015-11-11
  • Java toString方法重写工具之ToStringBuilder案例详解

    Java toString方法重写工具之ToStringBuilder案例详解

    这篇文章主要介绍了Java toString方法重写工具之ToStringBuilder案例详解,本篇文章通过简要的案例,讲解了该项技术的了解与使用,以下就是详细内容,需要的朋友可以参考下
    2021-08-08
  • springboot项目如何开启https服务

    springboot项目如何开启https服务

    这篇文章主要介绍了springboot项目如何开启https服务方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2025-04-04
  • Python安装Jupyter Notebook配置使用教程详解

    Python安装Jupyter Notebook配置使用教程详解

    这篇文章主要介绍了Python安装Jupyter Notebook配置使用教程详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-09-09
  • 解决Spring AOP拦截抽象类(父类)中方法失效问题

    解决Spring AOP拦截抽象类(父类)中方法失效问题

    这篇文章主要介绍了解决Spring AOP拦截抽象类(父类)中方法失效问题,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-11-11
  • SpringBoot启动过程逐步分析讲解

    SpringBoot启动过程逐步分析讲解

    这篇文章主要介绍了SpringBoot启动过程的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习吧
    2023-01-01
  • Netty源码分析NioEventLoop初始化线程选择器创建

    Netty源码分析NioEventLoop初始化线程选择器创建

    这篇文章主要介绍了Netty源码分析NioEventLoop初始化线程选择器创建,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-03-03
  • 解决IDEA创建maven项目时pom.xml没有变蓝的问题

    解决IDEA创建maven项目时pom.xml没有变蓝的问题

    这篇文章主要介绍了解决IDEA创建maven项目时pom.xml没有变蓝的问题,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2020-08-08
  • Java编写实现坦克大战小游戏

    Java编写实现坦克大战小游戏

    这篇文章主要为大家详细介绍了Java编写实现坦克大战小游戏,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2022-01-01

最新评论