简介
在当今高速发展的运用开发领域,关于进步体系功能和呼应才能的需求越来越火急。而异步编程作为一种处理方案,已经成为现代运用开发中的一项重要技术。本篇博客将带您深入探究 Java 中的 @Async 注解,提醒其强壮的异步履行才能和精妙的完结机制。
异步编程是一种编程形式,经过将使命分解为多个子使命,并在后台或并行线程中履行这些子使命,以进步程序的功能和呼应才能。
@Async 注解简介
@Async 注解是 Spring 结构提供的注解,用于将办法符号为异步履行的办法。它的作用是告诉 Spring 结构在调用被注解的办法时,将其放入线程池中异步履行,而不是堵塞等候办法的完结。
@Async 注解的作业原理是,在调用被注解的办法时,Spring 会将该办法的履行转移到线程池中的一个线程进行处理。履行完结后,办法的回来值将经过 Future 或 CompletableFuture 进行封装,以便获取办法的回来成果。
-
@Async 注解适用于以下场景,并具有以下优势:
- 网络恳求:在处理网络恳求时,能够运用 @Async 注解将恳求发送和呼应处理别离,进步体系的并发处理才能。
- 耗时计算:关于需求耗费大量时间的计算使命,能够运用 @Async 注解将计算进程放在后台履行,防止堵塞主线程,进步体系的呼应速度。
- 并行处理:经过 @Async 注解,能够同时履行多个使命,将多个彼此独立的使命并行处理,然后减少整体处理时间。
- 呼应才能进步:运用异步编程能够防止堵塞主线程,进步体系的并发才能和呼应才能,增强用户体会。
- 代码简化:运用 @Async 注解能够简化编程模型,将异步履行的逻辑与业务逻辑别离,使代码更明晰、易于保护。
异步履行经过将使命分解为多个并发履行的子使命,能够充分利用体系资源,进步体系的吞吐量和并发处理才能,然后进步体系的功能和呼应才能。@Async 注解简化了异步编程的完结,使开发人员能够更方便地运用异步处理机制。同时,它还能够使代码更易于阅读和保护,进步开发功率。
@Async 注解的源码解析
@Async 注解在 Spring 结构中的完结首要依赖于以下几个要害组件:
-
AsyncAnnotationBeanPostProcessor
:这是一个 Bean 后置处理器,担任解析带有 @Async 注解的办法,将其包装成异步使命。 -
AsyncTaskExecutor
:这是一个使命履行器,用于履行异步使命。能够经过装备来指定详细的线程池或使命调度器。 -
AsyncConfigurer
:这是一个可选的接口,用于提供自定义的异步使命履行器。
在 Spring 结构中,当启用异步支撑时,AsyncAnnotationBeanPostProcessor
会扫描容器中的 Bean,并检查其中的办法是否符号有 @Async 注解。假如发现带有 @Async 注解的办法,它将会将其封装成一个署理目标,并注册为一个可履行的异步使命。
当调用被 @Async 注解符号的办法时,实际上是调用了该办法的署理目标。署理目标会将办法的履行转移到线程池中的一个线程进行处理,并回来一个 Future 目标,用于获取办法的回来成果。
线程池的装备能够经过 Spring 的装备文件或编程方式进行指定。能够装备线程池的巨细、线程池的类型(如固定巨细线程池、缓存线程池等)以及使命调度战略等。
异步办法与业务的联系
在运用 @Async 注解符号的异步办法与业务之间存在一些联系和留意事项。
- 默许情况下,异步办法不受业务管理的影响。当一个带有 @Transactional 注解的办法调用一个符号为 @Async 的异步办法时,异步办法将在一个新的线程中履行,与原始办法的业务无关。
- 异步办法独立业务。假如希望异步办法能够参加到业务管理中,能够运用 Propagation.REQUIRES_NEW 传达行为。将异步办法设置为 @Transactional(propagation = Propagation.REQUIRES_NEW) ,这样异步办法将在新的业务中履行,与原始办法的业务阻隔开来。
- 异步办法和业务的提交。由于异步办法是在独立的线程中履行的,与原始办法的业务是别离的。因此,异步办法中的业务提交操作不会对原始办法的业务产生影响。即使异步办法中的业务提交失败,也不会导致原始办法的业务回滚。
- 异步办法和业务的反常处理。异步办法中的反常默许是不会被捕获和处理的,除非在异步办法中显式地进行了反常处理。假如需求对异步办法中的反常进行处理,能够运用 AsyncUncaughtExceptionHandler 接口来自定义反常处理逻辑。
需求留意的是,运用异步办法与业务的组合可能会带来一些潜在的问题和危险,如数据不一致性、并发冲突等。在运用异步办法和业务的同时,需求细心考虑业务需求和数据一致性的要求,保证逻辑正确性和数据完整性。
总结起来,异步办法和业务之间的联系能够经过设置业务的传达行为来调整。默许情况下,异步办法是独立于业务的,能够经过设置 Propagation.REQUIRES_NEW 传达行为使异步办法参加到业务管理中。然而,需求留意并发和数据一致性的问题,并依据详细业务需求合理运用异步办法和业务的组合。
Async反常处理
在运用 @Async进行异步办法调用时,反常处理是一个重要的方面。以下是异步办法的反常处理机制:
- 默许情况下,异步办法的反常会被捕获并封装为Future目标(或CompletableFuture目标)。您能够经过Future.get() 办法或CompletableFuture.get() 办法获取异步使命的成果,并在调用时捕获反常。假如异步使命抛出反常,将会在调用get() 办法时重新抛出反常,您能够在调用端进行反常处理。
@Async
public CompletableFuture<String> performTask() {
// 异步使命逻辑
}
// 调用异步办法并处理反常
CompletableFuture<String> future = myService.performTask();
try {
String result = future.get();
// 处理正常成果
} catch (InterruptedException | ExecutionException e) {
// 处理反常情况
}
- 您还能够运用AsyncUncaughtExceptionHandler接口来处理异步办法中未捕获的反常。经过完结AsyncUncaughtExceptionHandler接口,并在AsyncConfigurer中重写getAsyncUncaughtExceptionHandler() 办法,您能够定义全局的异步反常处理逻辑。
@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() 办法来处理异步办法中未捕获的反常。您能够在该办法中编写自定义的反常处理逻辑,例如日志记录、过错报警等。
经过上述反常处理机制,您能够捕获和处理异步办法中的反常,然后保证对异步使命的反常情况进行适当的处理。
ThreadLocal和Async运用问题
在作业进程中,经常遇到这个问题,体系通常会经过拦截器获取用户信息并设置到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 ThreadLocal<String> traceIdThreadLocal = new ThreadLocal<>();
private static final ThreadLocal<User> 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容器中。
经过以上装备,您能够运用不同的线程池类型为不同类型的异步使命装备不同的履行器,并依据需求调整线程池的属性。
最佳实践和留意事项
在运用异步办法时,需求留意以下几点:
- 异步办法应尽量坚持简单和独立,不涉及杂乱的业务逻辑。
- 异步办法的履行时间应控制在合理的范围内,防止因长期履行导致线程资源占用过多。
- 需求考虑异步办法与其他业务逻辑的协调,保证异步办法的履行顺序和成果正确性。
- 异步办法的并发性可能导致资源竞赛和并发访问的问题,需求进行适当的并发控制和线程安全处理。