SpringBoot中的异步编程—@Async
@Async 是什么
void test() {
A();
B();
C();
}
在没有Async的情况下,上面的方法是顺序执行的,也可以称为同步调用. B要在A执行完毕之后执行,C需要在B执行完毕之后执行,整个函数结束是在C执行完毕之后。
但是如果给B添加了@Async,执行顺序不变, 在执行完A之后,调用B,但是并不等待B完成,就执行C,C执行完毕之后,这个函数就执行完毕了.
在Java中,一般在处理类似的场景之时,都是基于创建独立的线程去完成相应的异步调用逻辑,通过主线程和不同的线程之间的执行流程,从而在启动独立的线程之后,主线程继续执行而不会产生停滞等待的情况。
什么时候需要添加@Async
根据业务需求,可以将暂时不需要获得处理的方法设置为@Async.
比如用户在前端点击完成了登录操作,这时候根据业务要求需要在登录成功之后进行埋点的处理.
其实埋点成功与否都不影响用户操作,这时候就可以将埋点方法设置为@Async.
个人认为此类任务通常有三个特征:
- 业务优先级低.
- 运行时间长,可能会造成卡顿.
- 返回结果暂时不立即处理,包括exception.
如何使用@Async
- 对当前Application添加@EnableAsync,当然也可以添加到Config,主要的是让Spring知道你有开启Async.
@EnableAsync
@SpringBootApplication
public class TestApplication {
public static void main(String[] args) {
SpringApplication.run(TestApplication.class, args);
}
}
- 对当前方法添加Async
@Async
public void B() {
}
- 注意事项 对Static方法修饰无效! 调用与被修饰方法不能写在同一个函数中。
比如:
class Test {
public void A() {
B(); // 这时候不会触发Async
}
@Async
public void B() {
}
}
所以需要拆开调用:
class Test {
public void A() {
TestB testB = new TestB();
testB.B();
}
}
class TestB {
@Async
public void B() {
}
}
- 返回值只能为Future或者Void, Future的使用方式.
@Async
public Future<String> asyncMethod() {
SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");//设置日期格式
System.out.println("StartTime - " + Thread.currentThread().getName() + df.format(new Date()));
try {
Thread.sleep(1000);
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("EndTime - " + Thread.currentThread().getName() + df.format(new Date()));
return new AsyncResult<String>("hello world!");
}
@Test
public void contextLoads() {
Future<String> result1 = test.asyncMethod();
Future<String> result2 = test.asyncMethod();
Future<String> result3 = test.asyncMethod();
while (!result1.isDone() || !result2.isDone() || !result3.isDone()) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
自定义线程池
@Configuration
public class TaskConfiguration {
@Bean("taskExecutor")
public Executor taskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(10); // 线程池创建时候初始化的线程数
executor.setMaxPoolSize(20); // 线程池最大的线程数,只有在缓冲队列满了之后,才会申请超过核心线程数的线程
executor.setQueueCapacity(200); // 缓冲任务队列的大小
executor.setKeepAliveSeconds(60); // 允许线程的空闲时间,超过会被销毁
executor.setThreadNamePrefix("custom-prefix-");// 线程的前缀
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); // 对拒绝任务的处理策略
return executor;
}
}
//定义之后在Async中指定
@Async("taskExecutor")
......
由于在应用关闭的时候异步任务还在执行,导致类似 数据库连接池 这样的对象一并被 销毁了 ,当 异步任务 中对 数据库 进行操作就会出错。
setWaitForTasksToCompleteOnShutdown(true): 该方法用来设置 线程池关闭 的时候 等待 所有任务都完成后,再继续 销毁 其他的 Bean,这样这些 异步任务 的 销毁 就会先于 数据库连接池对象 的销毁。 setAwaitTerminationSeconds(60): 该方法用来设置线程池中 任务的等待时间 ,如果超过这个时间还没有销毁就 强制销毁 ,以确保应用最后能够被关闭,而不是阻塞住。
作者: 嘻嘻z
原文:SpringBoot中的异步编程—@Async - 掘金