为了快速的将同步方法变为异步方法,Spring提供了@Async
和@EnableAsync
注解。两个注解搭配,就可以快速实现一个异步方法。
1 初步体验@Async注解
下面,我们假设有一个异步记录日志的方法,需要异步输出日志,代码如下。
定义异步日志输出类,需要是一个spring bean。
// 使用@Component,定义为一个spring bean。
// @EnableAsync开启spring异步执行能力
@Component
@EnableAsync
public class AsyncLogPrinter {
// @Async注解可以用在方法上,表示该方法将会异步执行
@Async
public void log(String msg) {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " log msg:" + msg);
}
}
定义spring bean配置文件appcontext-core.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd">
<context:component-scan base-package="com.chris" />
<context:annotation-config />
</beans>
定义main入口类
public class AsyncDemo {
public static void main(String[] args) {
// 由于在main方法里面,因此需要获取context,然后从context获取spring bean。
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(new String[]{"appcontext-core.xml"});
// 从spring context获取bean
AsyncLogPrinter asyncLogPrinter = context.getBean("asyncLogPrinter", AsyncLogPrinter.class);
long start = System.currentTimeMillis();
// 该方法会异步执行,不会阻塞main线程
asyncLogPrinter.log("hello");
System.out.println(System.currentTimeMillis() - start);
}
}
一种输出为
13
task-1 log msg:hello
从上面的例子可以看出,一个异步执行的方法通过两个注解就可以配置完成。图中例子,异步方法没有返回值,如果想要有返回值,Spring支持返回一个Future或者CompletableFuture。
2 @Async注解实现原理简介
从大致原理上来讲,spring容器启动时,会识别含有@EnableAsync注解和@Async注解的类;若发现某个方法上含有@Async注解,则为该方法所在的类创建AOP代理类,为该方法创建代理方法,代理方法将原方法包装为一个异步的方法。在运行时,代理方法将任务提交到线程池执行,并立即返回。
这只是大致的思路,我们会好奇,异步任务是在哪个线程池执行的?
ThreadPoolTaskExecutor
ThreadPoolTaskExecutor的类图大致如下(图中去掉了不妨碍理解ThreadPoolTaskExecutor的其他的类)
- TaskExecutor
该接口继承了java.util.concurrent.Executor
(jdk Executor),其只有一个方法@Override void execute(Runnable task);
。该接口的目的是将任务的提交和执行解耦,将任务的执行抽象出来,也就是说,用户提交任务只需要调用该接口就可以了,而任务的执行方式为异步、同步、使用线程池或者不使用线程池都可以。
至于为什么要继承jdk Executor,而不是直接使用jdk Executor呢?原因有两个:- 若直接使用jdk Executor,则会受到jdk Executor接口演变的影响,没有做到解耦合。
- 想要复用jdk Executor类已有的功能,则继承了jdk Executor。
- SyncTaskExecutor
TaskExecutor的一个实现类,会在调用线程中同步执行任务。由于在同一个线程中执行任务,因此线程上下文相同,主要用于测试场景。建议选择异步的执行器。 - AsyncTaskExecutor
接口类,TaskExecutor的一个实现类,一个异步执行器。其方法如下:除了继承自TaskExecutor的方法
void execute(Runnable task);
,AsyncTaskExecutor还额外提供了三个方法,其中execute(Runnable task, long startTimeout);
的形参含有超时时间。 - SchedulingTaskExecutor
接口类,继承了AsyncTaskExecutor,只有一个方法boolean prefersShortLivedTasks()
,表明了执行器是否希望提交的任务执行时间短。用户使用该类型的执行器时,能够知道该执行器的特点,从而提交适合该执行器的任务。 - AsyncListenableTaskExecutor
接口类,继承了AsyncTaskExecutor,扩展了AsyncTaskExecutor的能力,增加了返回ListenableFuture的能力。ListenableFuture继承了Future,可以接收已经完成了的Callback。当执行器添加Callback任务时,若发现Callback任务已经执行完成,则会立即触发Callback的回调函数。 - SimpleAsyncTaskExecutor
实现类,实现了AsyncListenableTaskExecutor。对于每个任务,该执行器都会新建一个线程,来异步的执行任务。默认情况下不限制新建的线程个数,但可以通过属性concurrencyLimit
来设置。由于对于每个任务都新建线程来执行任务,因此,不建议使用该执行器。 - ConcurrentTaskExecutor
实现类,是对于java.util.concurrent.Executor
执行器的一个包装,也就是说,该执行器可以认为是jdk Executor执行器。默认的构造器会使用Executors.newSingleThreadExecutor()
来创建一个执行器,当然用户也可以指定一个Executor执行器,体现出了该执行器的灵活性。对于一般的场景,可以使用spring已有的ThreadPoolTaskExecutor
来代替该执行器。 - ThreadPoolTaskExecutor 实现类,将ThreadPoolExecutor包装为一个spring bean。可以通过设置该bean的属性
corePoolSize
、maxPoolSize
、keepAliveSeconds
、queueCapacity
来控制执行器的特性,并且可以通过JMX来管理和监控执行器的特性。此外,可以在动态运行时修改参数corePoolSize
,maxPoolSize
和keepAliveSeconds
。 ThreadPoolTaskExecutor的默认配置为:corePoolSize=1,maxPoolSize不限制,queueCapacity不限制。
@EnableAsync注解和@Async注解
我们这里只是从大致原理讲述一下spring支持异步方法的实现原理:
- spring在启动时,会识别注解@EnableAsync和@Async,并为含有@EnableAsync注解的bean创建代理bean。默认的代理为Cglib代理,具体的拦截器为
AnnotationAsyncExecutionInterceptor
。 - 在运行时,实际运行的方法为代理bean的方法。
3 总结
本书讲述了spring框架的异步方法之怎么使用的,简述了spring怎么通过注解和动态代理来方便的支持异步方法的,并较详细的讲述了各个spring执行器的特点。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· 【自荐】一款简洁、开源的在线白板工具 Drawnix