SpringBoot使用异步调用
介绍
异步请求的处理。除了异步请求,一般上我们用的比较多的应该是异步调用。通常在开发过程中,会遇到一个方法是和实际业务无关的,没有紧密性的。比如记录日志信息等业务。这个时候正常就是启一个新线程去做一些业务处理,让主线程异步的执行其他业务。
使用方式:
-
在 Spring Framework 的 Spring Task 模块,提供了 @Async 注解,可以添加在方法上,自动实现该方法的异步调用
-
需要在启动类或配置类加上@EnableAsync使异步调用@Async注解生效
-
在需要异步执行的方法上加入此注解即可@Async(“threadPool”),threadPool为自定义线程池。(@Async默认使用SimpleAsyncTaskExecutor线程池。也可以根据Bean Name指定特定线程池)
注意事项:
-
在默认情况下,未设置TaskExecutor时,默认是使用SimpleAsyncTaskExecutor这个线程池,但此线程不是真正意义上的线程池,因为线程不重用,每次调用都会创建一个新的线程。可通过控制台日志输出可以看出,每次输出线程名都是递增的。所以最好我们来自定义一个线程池。
-
调用的异步方法,不能为同一个类的方法(包括同一个类的内部类),简单来说,因为Spring在启动扫描时会为其创建一个代理类,而同类调用时,还是调用本身的代理类的,所以和平常调用是一样的。
-
其他的注解如@Cache等也是一样的道理,说白了,就是Spring的代理机制造成的。所以在开发中,最好把异步服务单独抽出一个类来管理。
-
同步事务方法内部调用异步方法, 异步方法会新开启一个事务, 异步事务方法调用异步方法, 内部异步方法也会新开启事务。
扫描二维码关注公众号,回复: 15558123 查看本文章 -
同步事务方法调用异步方法, 异步方法报错, 同步方法不会回滚, 想同步方法回滚, 那就需要同步方法捕获异步方法的报错, 继续往外抛异常(异步方法必须带返回值)。
一些异步使用场景
文章阅读的业务逻辑 = 查询文章详情 + 更新文章阅读量后再响应客户端, 其实也无需等到阅读量更新后才响应文章详情给客户端, 用户查看文章是主要逻辑, 而文章阅读量更新是次要逻辑, 况且阅读量就算更新失败一点数据偏差也不会影响用户阅读因此这两个数据库操作之间的一致性是较弱的,这类都能用异步事件去优化。
@Async失效场景
- 异步方法使用static修饰
- 异步类没有使用@Component、@Service等注解,导致spring无法扫描到异步类
- 调用的异步方法,不能为同一个类的方法(包括同一个类的内部类)。PS:因为Spring在启动扫描时会为其创建一个代理类,而同类调用时,还是调用本身的代理类的,所以和平常调用是一样的
- 类中需要使用@Autowired或@Resource等注解自动注入,不能自己手动new对象
- 如果使用SpringBoot框架必须在启动类中增加@EnableAsync注解
- 在Async 方法上标注@Transactional是没用的。 在Async 方法调用的方法上标注@Transactional 有效。
同步与异步调用示例
- 异步调用,对应的是同步调用。
- 同步调用:指程序按照 定义顺序 依次执行,每一行程序都必须等待上一行程序执行完成之后才能执行;
- 异步调用:指程序在顺序执行时,不等待异步调用的语句返回结果,就执行后面的程序。
配置线程池参数
package com.xiaoge;
/**
* TODO
*
* @author <a href="mailto:[email protected]">Zhang Xiao</a>
* @since
*/
public interface AsyncConfigConstant {
/**
* 最大线程
*/
int MAX_SIZE = 6;
/**
* 核心线程
*/
int CORE_SIZE = 3;
/**
* 保持时间(默认秒)
*/
int KEEP_ALIVE = 3;
/**
* 队列容量
*/
int QUEUE_CAPACITY = 1000;
/**
* 名称前缀
*/
String THREAD_NAME_PREFIX = "async-task-thread-pool-";
}
线程bean
package com.xiaoge;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import java.util.concurrent.Executor;
import java.util.concurrent.ThreadPoolExecutor;
@Configuration
public class ExecutorConfig {
@Bean("taskExecutor")
public Executor myExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
// 核心线程
executor.setCorePoolSize(AsyncConfigConstant.CORE_SIZE);
// 最大线程
executor.setMaxPoolSize(AsyncConfigConstant.MAX_SIZE);
// 队列容量
executor.setQueueCapacity(AsyncConfigConstant.QUEUE_CAPACITY);
// 保持时间(默认秒)
executor.setKeepAliveSeconds(AsyncConfigConstant.KEEP_ALIVE);
// 名称前缀
executor.setThreadNamePrefix(AsyncConfigConstant.THREAD_NAME_PREFIX);
executor.setRejectedExecutionHandler( new ThreadPoolExecutor.AbortPolicy());
executor.initialize();
return executor;
}
}
同步service
package com.xiaoge;
import org.springframework.stereotype.Component;
import java.util.Random;
/**
* TODO
*
* @author <a href="mailto:[email protected]">Zhang Xiao</a>
* @since
*/
@Component
public class Task {
public static Random random =new Random();
public void doTaskOne() throws Exception {
System.out.println("开始做任务一");
long start = System.currentTimeMillis();
Thread.sleep(random.nextInt(10000));
long end = System.currentTimeMillis();
System.out.println("完成任务一,耗时:" + (end - start) + "毫秒");
}
public void doTaskTwo() throws Exception {
System.out.println("开始做任务二");
long start = System.currentTimeMillis();
Thread.sleep(random.nextInt(10000));
long end = System.currentTimeMillis();
System.out.println("完成任务二,耗时:" + (end - start) + "毫秒");
}
public void doTaskThree() throws Exception {
System.out.println("开始做任务三");
long start = System.currentTimeMillis();
Thread.sleep(random.nextInt(10000));
long end = System.currentTimeMillis();
System.out.println("完成任务三,耗时:" + (end - start) + "毫秒");
}
}
异步service
package com.xiaoge;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.AsyncResult;
import org.springframework.stereotype.Component;
import java.util.Random;
import java.util.concurrent.Future;
/**
* TODO
*
* @author <a href="mailto:[email protected]">Zhang Xiao</a>
* @since
*/
@Component
public class AsyncTask {
public static Random random =new Random();
@Async("taskExecutor")
public Future<String> doTaskOne() throws Exception {
System.out.println("开始做任务一");
long start = System.currentTimeMillis();
Thread.sleep(random.nextInt(10000));
long end = System.currentTimeMillis();
System.out.println("完成任务一,耗时:" + (end - start) + "毫秒");
return new AsyncResult<>("任务一完成");
}
@Async("taskExecutor")
public Future<String> doTaskTwo() throws Exception {
System.out.println("开始做任务二");
long start = System.currentTimeMillis();
Thread.sleep(random.nextInt(10000));
long end = System.currentTimeMillis();
System.out.println("完成任务二,耗时:" + (end - start) + "毫秒");
return new AsyncResult<>("任务二完成");
}
@Async("taskExecutor")
public Future<String> doTaskThree() throws Exception {
System.out.println("开始做任务三");
long start = System.currentTimeMillis();
Thread.sleep(random.nextInt(10000));
long end = System.currentTimeMillis();
System.out.println("完成任务三,耗时:" + (end - start) + "毫秒");
return new AsyncResult<>("任务三完成");
}
}
启动类
package com.xiaoge;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.scheduling.annotation.EnableAsync;
/**
* TODO
*
* @author <a href="mailto:[email protected]">Zhang Xiao</a>
* @since
*/
@EnableAsync
@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
public class AsyncApplication {
public static void main(String[] args) {
SpringApplication.run(AsyncApplication.class, args);
}
}
测试类
package com.xiaoge;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
/**
* TODO
*
* @author <a href="mailto:[email protected]">Zhang Xiao</a>
* @since
*/
@RunWith(SpringRunner.class)
@SpringBootTest(classes = AsyncApplication.class)
public class AsyncTestApplication {
@Autowired
private AsyncTask asyncTask;
@Autowired
private Task task;
/**
* 异步方法
* @throws Exception
*/
@Test
public void test() throws Exception {
asyncTask.doTaskOne();
asyncTask.doTaskTwo();
asyncTask.doTaskThree();
}
/**
* 同步方法
* @throws Exception
*/
@Test
public void test1() throws Exception {
task.doTaskOne();
task.doTaskTwo();
task.doTaskThree();
}
}