在当今高速发展的应用开发领域,对于提升系统性能和响应能力的需求越来越迫切。而异步编程作为一种解决方案,已经成为现代应用开发中的一项重要技术。本篇博客将带您深入探究 Java 中的 @Async 注解,揭示其强大的异步执行能力和精妙的实现机制。
异步编程是一种编程模式,通过将任务分解为多个子任务,并在后台或并行线程中执行这些子任务,以提高程序的性能和响应能力。
@Async 注解是 Spring 框架提供的注解,用于将方法标记为异步执行的方法。它的作用是告诉 Spring 框架在调用被注解的方法时,将其放入线程池中异步执行,而不是阻塞等待方法的完成。
@Async 注解的工作原理是,在调用被注解的方法时,Spring 会将该方法的执行转移到线程池中的一个线程进行处理。执行完成后,方法的返回值将通过 Future 或 CompletableFuture 进行封装,以便获取方法的返回结果。
@Async 注解适用于以下场景,并具有以下优势:
异步执行通过将任务分解为多个并发执行的子任务,可以充分利用系统资源,提高系统的吞吐量和并发处理能力,从而提升系统的性能和响应能力。@Async 注解简化了异步编程的实现,使开发人员能够更方便地使用异步处理机制。同时,它还可以使代码更易于阅读和维护,提高开发效率。
@Async 注解在 Spring 框架中的实现主要依赖于以下几个关键组件:
在 Spring 框架中,当启用异步支持时,AsyncAnnotationBeanPostProcessor 会扫描容器中的 Bean,并检查其中的方法是否标记有 @Async 注解。如果发现带有 @Async 注解的方法,它将会将其封装成一个代理对象,并注册为一个可执行的异步任务。
当调用被 @Async 注解标记的方法时,实际上是调用了该方法的代理对象。代理对象会将方法的执行转移到线程池中的一个线程进行处理,并返回一个 Future 对象,用于获取方法的返回结果。
线程池的配置可以通过 Spring 的配置文件或编程方式进行指定。可以配置线程池的大小、线程池的类型(如固定大小线程池、缓存线程池等)以及任务调度策略等。
在使用 @Async 注解标记的异步方法与事务之间存在一些关系和注意事项。
需要注意的是,使用异步方法与事务的组合可能会带来一些潜在的问题和风险,如数据不一致性、并发冲突等。在使用异步方法和事务的同时,需要仔细考虑业务需求和数据一致性的要求,确保逻辑正确性和数据完整性。
总结起来,异步方法和事务之间的关系可以通过设置事务的传播行为来调整。默认情况下,异步方法是独立于事务的,可以通过设置 Propagation.REQUIRES_NEW 传播行为使异步方法参与到事务管理中。然而,需要注意并发和数据一致性的问题,并根据具体业务需求合理使用异步方法和事务的组合。
在使用 @Async进行异步方法调用时,异常处理是一个重要的方面。以下是异步方法的异常处理机制:
@Async public CompletableFutureperformTask() { // 异步任务逻辑 } // 调用异步方法并处理异常 CompletableFuture future = myService.performTask(); try { String result = future.get(); // 处理正常结果 } catch (InterruptedException | ExecutionException e) { // 处理异常情况 }
@Configuration @EnableAsync public class AsyncConfig implements AsyncConfigurer { // 配置异步方法执行器 @Override public Executor getAsyncExecutor() { // 配置任务执行器 } // 配置异步方法未捕获异常处理器 @Override public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() { return new CustomAsyncExceptionHandler(); } // 其他配置... }
public class CustomAsyncExceptionHandler implements AsyncUncaughtExceptionHandler { @Override public void handleUncaughtException(Throwable ex, Method method, Object... params) { // 处理异步方法未捕获的异常 Class> clazz = method.getDeclaringClass(); String message = String.format("异步方法执行失败,具体类名: %s, 方法名:%s, 异常信息: %s", clazz.getName(), method.getName(), ex); log.error("异步方法执行失败,具体类名: {}, 方法名:{}, 方法入参:{}, 异常信息: {}", clazz.getName(), method.getName(), Arrays.toString(params), ex.getMessage(), ex); } }
在上述示例中,CustomAsyncExceptionHandler实现了AsyncUncaughtExceptionHandler接口,并实现了handleUncaughtException() 方法来处理异步方法中未捕获的异常。您可以在该方法中编写自定义的异常处理逻辑,例如日志记录、错误报警等。
通过上述异常处理机制,您可以捕获和处理异步方法中的异常,从而确保对异步任务的异常情况进行适当的处理。
在工作过程中,经常遇到这个问题,系统通常会通过拦截器获取用户信息并设置到ThreadLoacl中,但是在异步方法中获取用户信息,却出现了获取到了其他用户信息的问题。
这是因为@Async注解会在异步执行方法时切换线程,而线程切换会导致ThreadLocal中的内容无法被正确传递。
解决这个问题的一种方法是使用AsyncTaskExecutor的子类,例如ThreadPoolTaskExecutor,并在配置中设置TaskDecorator。TaskDecorator可以在每次异步任务执行时对线程进行修饰,以确保ThreadLocal中的内容被正确传递。
以下是一个示例配置的代码片段:
@Configuration @EnableAsync public class AsyncConfig implements AsyncConfigurer { @Override public Executor getAsyncExecutor() { ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); executor.setTaskDecorator(new ThreadLocalTaskDecorator()); // 设置TaskDecorator executor.initialize(); return executor; } @Override public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() { return new CustomAsyncExceptionHandler(); } // 自定义的TaskDecorator private static class ThreadLocalTaskDecorator implements TaskDecorator { @Override public Runnable decorate(Runnable runnable) { // 保存当前线程的ThreadLocal内容 // 获取调用线程的 traceId 和用户信息 String traceId = ThreadLocalUtils.getTraceId(); User user = ThreadLocalUtils.getUser(); return () -> { try { // 恢复之前保存的ThreadLocal内容 // 在子线程中设置 traceId 和用户信息 ThreadLocalUtils.setTraceId(traceId); ThreadLocalUtils.setUser(user); runnable.run(); } finally { // 清除子线程的 traceId 和用户信息 ThreadLocalUtils.clear(); } }; } } }
在上述示例中,我们使用ThreadLocalContextHolder类来管理ThreadLocal的操作,包括设置、获取和清理ThreadLocal中的内容。
public class ThreadLocalUtils { private static final ThreadLocaltraceIdThreadLocal = new ThreadLocal<>(); private static final ThreadLocal userThreadLocal = new ThreadLocal<>(); public static String getTraceId() { return traceIdThreadLocal.get(); } public static void setTraceId(String traceId) { traceIdThreadLocal.set(traceId); } public static User getUser() { return userThreadLocal.get(); } public static void setUser(User user) { userThreadLocal.set(user); } public static void clear() { traceIdThreadLocal.remove(); userThreadLocal.remove(); } }
通过使用以上的配置和ThreadLocalTaskDecorator,你可以确保在异步执行时,ThreadLocal中的用户信息能够正确传递并被获取到。
如果您需要配置多个不同类型的 @Async注解,并且使用不同的线程池类型(缓存线程池和固定线程池),可以按照以下方式进行配置:
首先,创建多个线程池和相应的TaskExecutor bean。
@Configuration @EnableAsync public class AsyncConfig implements AsyncConfigurer { // 缓存线程池 @Bean("cachedThreadPool") public TaskExecutor cachedThreadPool() { ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); executor.setCorePoolSize(0); // 根据实际情况调整核心线程数 executor.setMaxPoolSize(Integer.MAX_VALUE); // 根据实际情况调整最大线程数 executor.setQueueCapacity(100); // 根据实际情况调整队列容量 executor.setThreadNamePrefix("cached-thread-"); executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); executor.setTaskDecorator(new MdcTaskDecorator()); // 设置任务装饰器 executor.initialize(); return executor; } // 固定线程池 @Bean("fixedThreadPool") public TaskExecutor fixedThreadPool() { ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); executor.setCorePoolSize(10); // 根据实际情况调整核心线程数 executor.setMaxPoolSize(10); // 根据实际情况调整最大线程数 executor.setQueueCapacity(0); // 不使用队列,直接执行 executor.setThreadNamePrefix("fixed-thread-"); executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); executor.setTaskDecorator(new MdcTaskDecorator()); // 设置任务装饰器 executor.initialize(); return executor; } // 配置异步方法执行器 @Override public Executor getAsyncExecutor() { return cachedThreadPool(); } // 配置自定义的异步方法执行器,用于特定类型的异步任务 @Bean("customAsyncExecutor") public Executor customAsyncExecutor() { return fixedThreadPool(); } // 其他配置... }
在上述示例中,我们创建了两个不同类型的线程池:cachedThreadPool和fixedThreadPool,并将它们作为TaskExecutor bean 注册到Spring容器中。
通过以上配置,您可以使用不同的线程池类型为不同类型的异步任务配置不同的执行器,并根据需求调整线程池的属性。
在使用异步方法时,需要注意以下几点:
github: https://github.com/kong0827
博客: https://juejin.cn/user/3403743731154190/posts