前言

  众所周知,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

最新文章

  1. Web 开发人员和设计师必读文章推荐【系列三十】
  2. [AngularJS] 使用AngularCSS动态加载CSS
  3. Redis 读后小感
  4. 【转】微信公众账号 Senparc.Weixin.MP SDK 开发教程 索引
  5. BZOJ3720 Gty的妹子树
  6. 使用AppCan自带的升级功能实现移动端升级
  7. ubuntu 配置TFTP和NFS
  8. 常用的 Android Studio 快捷键
  9. 解决 ListView 水平滚动条不出现的问题(转载)
  10. 通过web代理进行跨域访问,http请求返回超时的问题定位
  11. ARCGIS获取图层下的要素信息及要素类转换为要素
  12. ZOJ 2724 Windows 消息队列 (优先队列)
  13. 如何修改HTML5 input placeholder 颜色
  14. windows系统下的特殊目录导致的FileNotFoundException
  15. 从零开始学安全(三十八)●cobaltstrike生成木马抓肉鸡
  16. 【译】9. Java反射——泛型
  17. Python Redis 常用操作
  18. bzoj5093:[Lydsy1711月赛]图的价值
  19. linux系统挂载NTFS移动硬盘
  20. Spring Security静态资源访问

热门文章

  1. CEGUI 0.7.7 VS2010+SP3 编译过程
  2. select Demo
  3. ES 20 - 查询Elasticsearch中的数据 (基于DSL查询, 包括查询校验match + bool + term)
  4. awk数组统计
  5. 如何使用jQuery可以让滚轮滚到底部可以自动加载所需内容
  6. 使用ln -s解决库冲突的问题
  7. 直播技术:从性能参数到业务大数据,浅谈直播CDN服务监控
  8. HBase 学习之路(十一)—— Spring/Spring Boot + Mybatis + Phoenix 整合
  9. spring cloud 系列第2篇 —— eureka 高可用注册中心的搭建 (F版本)
  10. RocketMQ(6)---发送普通消息(三种方式)