异步中异常那点事

我接触异步概念最早的时候是在大学学习前端中,是一个很老的技术叫Ajax。异步编程很重要!很重要!很重要!在接口性能优化中我常用“sql优化、业务优化、算法优化”三把斧。其中“业务优化”中有“是否有可以异步进行的”这么一小则。今天的主题我们来看看Spring中相关异步中异常的那点事。

本文不是异步入门,默认读者有初步的异步知识。还不懂异步的读者请先网上查找相关资料先了解。

异步方法有两种形式:

  • 无返回值:void
  • 有返回值:可以声明常规的java.util.concurrent.Future返回类型,还可以声明 Spring 的org.springframework.util.concurrent.ListenableFuture,或者声明为 Spring 4.2 的 JDK 8 的java.util.concurrent.CompletableFuture:与异步任务进行更丰富的交互,并通过进一步的处理步骤立即进行组合。推荐CompletableFuture封装返回值

CompletableFuture是个好东西,网上资料也很多。推荐读者学习!

这两种方式的不同,导致了异步方法中异常管理不同。在有返回值的异步方法,调用者可以在调用get阻塞时捕获异步方法中的抛出的异常并进行处理。但是对于无返回值的异步方法,则不能如此了。那我们该怎么办呢?

我们先来看一下异步的例子。定义一个异步任务组件并交由spring管理

@Component
@Slf4j
public class AsyncTask {
  
    /**
     * 有返回值的异步方法
     */
    @Async("honeyAsyncExecutor")
    public CompletableFuture<Integer> asyncFuture() throws InterruptedException {
        log.info("start async task");
        Thread.sleep(1000)
        // 故意抛出异常
        int a = 1 % 0;
        log.info("end async task");
        return CompletableFuture.completedFuture(2);
    }


    /**
     * 无返回值的异步方法
     */
    @Async("honeyAsyncExecutor")
    public void async() throws InterruptedException {
        log.info("start async task");
        Thread.sleep(1000);
        // 故意抛出异常
        int a = 1 % 0;
        log.info("end async task");
    }
}

测试类

@Slf4j
@SpringBootTest
@RunWith(SpringRunner.class)
public class AsyncTest {
    @Autowired
    private AsyncTask asyncTask;

    @Test
    public void asyncTest() throws InterruptedException {
        log.info("start.....");
        CompletableFuture<Integer> future = asyncTask.asyncFuture();
        log.info("start main bs");
        Thread.sleep(2000);
        log.info("main bs end...");
        log.info("start request the result");
        try {
            Integer integer = future.get();
            log.info(String.valueOf(integer));
        } catch (ExecutionException exception) {
            log.info("catch the exception");
        }
    }
}

在这里插入图片描述
在结果中我们成功捕获了异常并进行了处理。注意:异步方法中抛出的异常会被包装成ExecutionException。

我们接着看一下没有返回值的异常该怎么捕获并处理

@Slf4j
@SpringBootTest
@RunWith(SpringRunner.class)
public class AsyncTest {
    @Autowired
    private AsyncTask asyncTask;

	@Test
    public void testAsync() throws InterruptedException {
        log.info("start.....");
        asyncTask.async();
        log.info("start main bs");
        Thread.sleep(2000);
        log.info("main bs end...");
    }
}

在这里插入图片描述
结果中看到了SimpleAsyncUncaughtExceptionHandler这么个类。我们具体看一下这个类。
这个类是spring默认提供的针对没有返回值抛出异常时捕获到异常后,仅仅是打印,其他啥都没干。

public class SimpleAsyncUncaughtExceptionHandler implements AsyncUncaughtExceptionHandler {

	private static final Log logger = LogFactory.getLog(SimpleAsyncUncaughtExceptionHandler.class);


	@Override
	public void handleUncaughtException(Throwable ex, Method method, Object... params) {
		if (logger.isErrorEnabled()) {
			logger.error("Unexpected exception occurred invoking async method: " + method, ex);
		}
	}
}

相信读者看到这里,应该懂该怎么针对性处理无返回值异步方法的异常了。就是自定义一个我们的AsyncUncaughtExceptionHandler 。但是在这里,博主并不推荐直接像spring那样实现AsyncUncaughtExceptionHandler 接口来提供我们的异步未捕获异常处理器。如果我们使用异步,我们要养成这么一个良好习惯:我们要根据我们系统的运行情况、配置情况等配置AsyncExecutor。所以比较好的做法是实现AsyncConfigurer 接口,在接口里提供AsyncExecutor,并提供上面的AsyncUncaughtExceptionHandler 。并在异步方法上面的异步注解指定Executor。

getAsyncExecutor前面为什么加个@Bean是为了可以给@Async指定Executor

@Slf4j
@Configuration
public class HoneyAsyncConfigurer implements AsyncConfigurer {

    @Bean("honeyAsyncExecutor")
    @Override
    public Executor getAsyncExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(8);
        executor.setMaxPoolSize(16);
        executor.setQueueCapacity(64);
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        executor.setThreadNamePrefix("HoneyAsyncThread-");
        return executor;
    }

    @Override
    public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
        return (Throwable ex, Method method, Object... params) -> {
            String errorBuilder = "Async execution error on method:" + method.toString() + " with parameters:"
                    + Arrays.toString(params);
            log.error(errorBuilder);
        };
    }
}

这里博主也没进行什么特殊处理,为了演示,仅仅是打印而已。在博主的Honey-OSS存储服务中,针对异步上传中也有涉及没有没有返回值的异步方法,在遇到预期中的异常时(像网络导致的链接异常)会利用重试机制进行重试。

说到这个重试,博主封装了基于注解的重试机制工具。后面分享出来。

在这里插入图片描述
在这里插入图片描述

  • 3
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
在Java异步编程,出现异常是很常见的情况。异步编程的特点是任务的执行和结果的返回是分离的,因此异常处理也需要特别注意。以下是一些Java异步异常处理的建议: 1. 使用CompletableFuture的exceptionally方法处理异常。CompletableFuture是Java 8新增的异步编程工具,它提供了exceptionally方法来处理异常情况。当Future执行过程抛出异常时,exceptionally方法会捕获异常并返回一个新的Future对象,该对象的结果为指定的默认值或者提供的异常信息。代码示例: ``` CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> { // 执行异步任务 return 1 / 0; }).exceptionally(ex -> { // 异常处理 System.out.println("异步任务执行出错:" + ex.getMessage()); return 0; }); ``` 2. 使用CompletableFuture的handle方法处理异常。handle方法和exceptionally方法类似,不同的是handle方法可以处理正常结果和异常结果。当Future执行过程抛出异常时,handle方法会捕获异常并返回一个新的Future对象,该对象的结果为指定的默认值或者提供的异常信息。代码示例: ``` CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> { // 执行异步任务 return 1 / 0; }).handle((result, ex) -> { if (ex != null) { // 异常处理 System.out.println("异步任务执行出错:" + ex.getMessage()); return 0; } else { // 正常结果处理 return result; } }); ``` 3. 使用try-catch语句捕获异常。在异步编程,任务的执行往往是在另外一个线程进行的,因此try-catch语句可能无法捕获异常。如果需要在异步任务捕获异常,可以使用CompletableFuture的completeExceptionally方法抛出异常。代码示例: ``` CompletableFuture<Integer> future = new CompletableFuture<>(); CompletableFuture.runAsync(() -> { try { // 执行异步任务 int result = 1 / 0; future.complete(result); } catch (Exception ex) { // 异常处理 future.completeExceptionally(ex); } }); ``` 需要注意的是,如果在异步任务抛出异常,需要使用completeExceptionally方法抛出异常,否则异步任务的返回值将被视为正常结果。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

啊杰eboy

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值