SpringBoot系列——@Async优雅的异步调用
前言
众所周知,java的代码是同步顺序执行,当我们需要执行异步操作时我们需要创建一个新线程去执行,以往我们是这样操作的:
/**
* 任务类
*/
class Task implements Runnable { @Override
public void run() {
System.out.println(Thread.currentThread().getName() + ":异步任务");
}
}
//新建线程并执行任务类
new Thread(new Task()).start();
jdk1.8之后可以使用Lambda 表达式
//新建线程并执行任务类
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + ":异步任务");
}).start();
当然,除了显式的new Thread,我们一般通过线程池获取线程,这里就不再展开
Spring 3.0之后提供了一个@Async注解,使用@Async注解进行优雅的异步调用,我们先看一下API对这个注解的定义:https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/scheduling/annotation/Async.html
本文记录在SpringBoot项目中使用@Async注解,实现优雅的异步调用
代码与测试
项目工程结构
因为要测试事务,所以需要引入
<!--添加springdata-jpa依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency> <!--添加MySQL驱动依赖 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
在启动类开启启用异步调用,同时注入ApplicationRunner对象在启动类进行调用测试
package cn.huanzi.qch.springbootasync; import cn.huanzi.qch.springbootasync.service.TestService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.ApplicationRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.stereotype.Component; @Component
@EnableAsync//开启异步调用
@SpringBootApplication
public class SpringbootAsyncApplication { @Autowired
private TestService testService; public static void main(String[] args) {
SpringApplication.run(SpringbootAsyncApplication.class, args);
} /**
* 启动成功
*/
@Bean
public ApplicationRunner applicationRunner() {
return applicationArguments -> {
long startTime = System.currentTimeMillis();
System.out.println(Thread.currentThread().getName() + ":开始调用异步业务");
//无返回值
// testService.asyncTask(); //有返回值,但主线程不需要用到返回值
// Future<String> future = testService.asyncTask("huanzi-qch");
//有返回值,且主线程需要用到返回值
// System.out.println(Thread.currentThread().getName() + ":返回值:" + testService.asyncTask("huanzi-qch").get()); //事务测试,事务正常提交
// testService.asyncTaskForTransaction(false);
//事务测试,模拟异常事务回滚
// testService.asyncTaskForTransaction(true); long endTime = System.currentTimeMillis();
System.out.println(Thread.currentThread().getName() + ":调用异步业务结束,耗时:" + (endTime - startTime));
};
}
}
看一下我们的测试业务类TestService
package cn.huanzi.qch.springbootasync.service; import java.util.concurrent.Future; public interface TestService {
/**
* 异步调用,无返回值
*/
void asyncTask(); /**
* 异步调用,有返回值
*/
Future<String> asyncTask(String s); /**
* 异步调用,无返回值,事务测试
*/
void asyncTaskForTransaction(Boolean exFlag);
}
package cn.huanzi.qch.springbootasync.service; import cn.huanzi.qch.springbootasync.pojo.TbUser;
import cn.huanzi.qch.springbootasync.repository.TbUserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.AsyncResult;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import java.util.concurrent.Future; @Service
public class TestServiceImpl implements TestService { @Autowired
private TbUserRepository tbUserRepository; @Async
@Override
public void asyncTask() {
long startTime = System.currentTimeMillis();
try {
//模拟耗时
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
long endTime = System.currentTimeMillis();
System.out.println(Thread.currentThread().getName() + ":void asyncTask(),耗时:" + (endTime - startTime));
} @Async("asyncTaskExecutor")
@Override
public Future<String> asyncTask(String s) {
long startTime = System.currentTimeMillis();
try {
//模拟耗时
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
long endTime = System.currentTimeMillis();
System.out.println(Thread.currentThread().getName() + ":Future<String> asyncTask(String s),耗时:" + (endTime - startTime));
return AsyncResult.forValue(s);
} @Async("asyncTaskExecutor")
@Transactional
@Override
public void asyncTaskForTransaction(Boolean exFlag) {
//新增一个用户
TbUser tbUser = new TbUser();
tbUser.setUsername("huanzi-qch");
tbUser.setPassword("123456");
tbUserRepository.save(tbUser); if(exFlag){
//模拟异常
throw new RuntimeException("模拟异常");
}
}
}
配置线程池
package cn.huanzi.qch.springbootasync.config; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.task.AsyncTaskExecutor;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; /**
* 线程池的配置
*/
@Configuration
public class AsyncConfig { private static final int MAX_POOL_SIZE = 50; private static final int CORE_POOL_SIZE = 20; @Bean("asyncTaskExecutor")
public AsyncTaskExecutor asyncTaskExecutor() {
ThreadPoolTaskExecutor asyncTaskExecutor = new ThreadPoolTaskExecutor();
asyncTaskExecutor.setMaxPoolSize(MAX_POOL_SIZE);
asyncTaskExecutor.setCorePoolSize(CORE_POOL_SIZE);
asyncTaskExecutor.setThreadNamePrefix("async-task-thread-pool-");
asyncTaskExecutor.initialize();
return asyncTaskExecutor;
}
}
配置好后,@Async会默认从线程池获取线程,当然也可以显式的指定@Async("asyncTaskExecutor")
无返回值
/**
* 启动成功
*/
@Bean
public ApplicationRunner applicationRunner() {
return applicationArguments -> {
long startTime = System.currentTimeMillis();
System.out.println(Thread.currentThread().getName() + ":开始调用异步业务");
//无返回值
testService.asyncTask();
long endTime = System.currentTimeMillis();
System.out.println(Thread.currentThread().getName() + ":调用异步业务结束,耗时:" + (endTime - startTime));
};
}
有返回值
有返回值,但主线程不需要用到返回值
/**
* 启动成功
*/
@Bean
public ApplicationRunner applicationRunner() {
return applicationArguments -> {
long startTime = System.currentTimeMillis();
System.out.println(Thread.currentThread().getName() + ":开始调用异步业务");//有返回值,但主线程不需要用到返回值
Future<String> future = testService.asyncTask("huanzi-qch"); long endTime = System.currentTimeMillis();
System.out.println(Thread.currentThread().getName() + ":调用异步业务结束,耗时:" + (endTime - startTime));
};
}
有返回值,且主线程需要用到返回值
/**
* 启动成功
*/
@Bean
public ApplicationRunner applicationRunner() {
return applicationArguments -> {
long startTime = System.currentTimeMillis();
System.out.println(Thread.currentThread().getName() + ":开始调用异步业务");
//有返回值,且主线程需要用到返回值
System.out.println(Thread.currentThread().getName() + ":返回值:" + testService.asyncTask("huanzi-qch").get()); long endTime = System.currentTimeMillis();
System.out.println(Thread.currentThread().getName() + ":调用异步业务结束,耗时:" + (endTime - startTime));
};
}
可以发现,有返回值的情况下,虽然异步业务逻辑是由新线程执行,但如果在主线程操作返回值对象,主线程会等待,还是顺序执行
事务测试
为了方便观察、测试,我们在配置文件中将日志级别设置成debug
#修改日志登记,方便调试
logging.level.root=debug
事务提交
/**
* 启动成功
*/
@Bean
public ApplicationRunner applicationRunner() {
return applicationArguments -> {
long startTime = System.currentTimeMillis();
System.out.println(Thread.currentThread().getName() + ":开始调用异步业务");//事务测试,事务正常提交
testService.asyncTaskForTransaction(false); long endTime = System.currentTimeMillis();
System.out.println(Thread.currentThread().getName() + ":调用异步业务结束,耗时:" + (endTime - startTime));
};
}
模拟异常,事务回滚
/**
* 启动成功
*/
@Bean
public ApplicationRunner applicationRunner() {
return applicationArguments -> {
long startTime = System.currentTimeMillis();
System.out.println(Thread.currentThread().getName() + ":开始调用异步业务");
//事务测试,模拟异常事务回滚
testService.asyncTaskForTransaction(true); long endTime = System.currentTimeMillis();
System.out.println(Thread.currentThread().getName() + ":调用异步业务结束,耗时:" + (endTime - startTime));
};
}
后记
SpringBoot使用@Async优雅的异步调用就暂时记录到这里,以后再进行补充
代码开源
代码已经开源、托管到我的GitHub、码云:
GitHub:https://github.com/huanzi-qch/springBoot
码云:https://gitee.com/huanzi-qch/springBoot
最新文章
- Web 开发人员和设计师必读文章推荐【系列三十】
- [AngularJS] 使用AngularCSS动态加载CSS
- Redis 读后小感
- 【转】微信公众账号 Senparc.Weixin.MP SDK 开发教程 索引
- BZOJ3720 Gty的妹子树
- 使用AppCan自带的升级功能实现移动端升级
- ubuntu 配置TFTP和NFS
- 常用的 Android Studio 快捷键
- 解决 ListView 水平滚动条不出现的问题(转载)
- 通过web代理进行跨域访问,http请求返回超时的问题定位
- ARCGIS获取图层下的要素信息及要素类转换为要素
- ZOJ 2724 Windows 消息队列 (优先队列)
- 如何修改HTML5 input placeholder 颜色
- windows系统下的特殊目录导致的FileNotFoundException
- 从零开始学安全(三十八)●cobaltstrike生成木马抓肉鸡
- 【译】9. Java反射——泛型
- Python Redis 常用操作
- bzoj5093:[Lydsy1711月赛]图的价值
- linux系统挂载NTFS移动硬盘
- Spring Security静态资源访问
热门文章
- CEGUI 0.7.7 VS2010+SP3 编译过程
- select Demo
- ES 20 - 查询Elasticsearch中的数据 (基于DSL查询, 包括查询校验match + bool + term)
- awk数组统计
- 如何使用jQuery可以让滚轮滚到底部可以自动加载所需内容
- 使用ln -s解决库冲突的问题
- 直播技术:从性能参数到业务大数据,浅谈直播CDN服务监控
- HBase 学习之路(十一)—— Spring/Spring Boot + Mybatis + Phoenix 整合
- spring cloud 系列第2篇 —— eureka 高可用注册中心的搭建 (F版本)
- RocketMQ(6)---发送普通消息(三种方式)