## 1\. 环境准备
在`Spring Boot`入口类上配置`@EnableAsync`注解开启异步处理。
![](https://img.kancloud.cn/75/cb/75cb7954f657557d7c2e7ab2cf50e708_936x250.png)
创建任务抽象类`AbstractTask`,并分别配置三个任务方法`doTaskOne()`,`doTaskTwo()`,`doTaskThree()`。
![](https://img.kancloud.cn/4f/a0/4fa0d1adcc03eb2386722f63709b8963_469x191.png)
~~~
public abstract class AbstractTask {
private static Random random = new Random();
public void doTaskOne() throws Exception {
System.out.println("开始做任务一");
long start = currentTimeMillis();
sleep(random.nextInt(10000));
long end = currentTimeMillis();
System.out.println("完成任务一,耗时:" + (end - start) + "毫秒");
}
public void doTaskTwo() throws Exception {
System.out.println("开始做任务二");
long start = currentTimeMillis();
sleep(random.nextInt(10000));
long end = currentTimeMillis();
System.out.println("完成任务二,耗时:" + (end - start) + "毫秒");
}
public void doTaskThree() throws Exception {
System.out.println("开始做任务三");
long start = currentTimeMillis();
sleep(random.nextInt(10000));
long end = currentTimeMillis();
System.out.println("完成任务三,耗时:" + (end - start) + "毫秒");
}
}
~~~
## 2\. 同步调用
下面通过一个简单示例来直观的理解什么是同步调用:
* 定义`Task`类,继承`AbstractTask`,三个处理函数分别模拟三个执行任务的操作,操作消耗时间随机取(`10`秒内)。
* ![](https://img.kancloud.cn/77/fe/77fe76d52a910585ec2d0ceb06504288_379x131.png)
~~~
@Component
public class SyncTask extends AbstractTask {
}
~~~
* 在**单元测试**用例中,注入`SyncTask`对象,并在测试用例中执行`doTaskOne()`,`doTaskTwo()`,`doTaskThree()`三个方法。
![](https://img.kancloud.cn/18/5c/185c428afb4517db72af23dbfe34198f_456x304.png)
~~~
@RunWith(SpringRunner.class)
@SpringBootTest
public class TaskTest {
@Autowired
private SyncTask task;
@Test
public void testSyncTasks() throws Exception {
task.doTaskOne();
task.doTaskTwo();
task.doTaskThree();
}
}
~~~
* 执行单元测试,可以看到类似如下输出:
~~~
开始做任务一
完成任务一,耗时:6720毫秒
开始做任务二
完成任务二,耗时:6604毫秒
开始做任务三
完成任务三,耗时:9448毫秒
~~~
任务一、任务二、任务三顺序的执行完了,换言之`doTaskOne()`,`doTaskTwo()`,`doTaskThree()`三个方法顺序的执行完成。
## 3\. 异步调用
上述的**同步调用**虽然顺利的执行完了三个任务,但是可以看到**执行时间比较长**,若这三个任务本身之间**不存在依赖关系**,可以**并发执行**的话,同步调用在**执行效率**方面就比较差,可以考虑通过**异步调用**的方式来**并发执行**。
* 创建`AsyncTask`类,分别在方法上配置`@Async`注解,将原来的**同步方法**变为**异步方法**。
![](https://img.kancloud.cn/b5/30/b530253da4fae7856cac85352e282451_465x233.png)
~~~
@Component
public class AsyncTask extends AbstractTask {
@Async
public void doTaskOne() throws Exception {
super.doTaskOne();
}
@Async
public void doTaskTwo() throws Exception {
super.doTaskTwo();
}
@Async
public void doTaskThree() throws Exception {
super.doTaskThree();
}
}
~~~
* 在**单元测试**用例中,注入`AsyncTask`对象,并在测试用例中执行`doTaskOne()`,`doTaskTwo()`,`doTaskThree()`三个方法。
~~~
@Autowired
private AsyncTask asyncTask;
@Test
public void testAsyncTasks() throws Exception {
asyncTask.doTaskOne();
asyncTask.doTaskTwo();
asyncTask.doTaskThree();
}
~~~
* 执行单元测试,可以看到类似如下输出:
~~~
开始做任务三
开始做任务一
开始做任务二
~~~
如果反复执行单元测试,可能会遇到各种不同的结果,比如:
1. 没有任何任务相关的输出
2. 有部分任务相关的输出
3. 乱序的任务相关的输出
原因是目前`doTaskOne()`,`doTaskTwo()`,`doTaskThree()`这三个方法已经**异步执行**了。主程序在**异步调用**之后,主程序并不会理会这三个函数是否执行完成了,由于没有其他需要执行的内容,所以程序就**自动结束**了,导致了**不完整**或是**没有输出任务**相关内容的情况。
> 注意:@Async所修饰的函数不要定义为static类型,这样异步调用不会生效。
### 4\. 异步回调
为了让`doTaskOne()`,`doTaskTwo()`,`doTaskThree()`能正常结束,假设我们需要统计一下三个任务**并发执行**共耗时多少,这就需要等到上述三个函数都完成动用之后记录时间,并计算结果。
那么我们如何判断上述三个**异步调用**是否已经执行完成呢?我们需要使用`Future<T>`来返回**异步调用**的**结果**。
* 创建`AsyncCallBackTask`类,声明`doTaskOneCallback()`,`doTaskTwoCallback()`,`doTaskThreeCallback()`三个方法,对原有的三个方法进行包装。
![](https://img.kancloud.cn/45/7b/457b55b8b34a2d10d6092987377880a1_474x359.png)
~~~
@Component
public class AsyncCallBackTask extends AbstractTask {
@Async
public Future<String> doTaskOneCallback() throws Exception {
super.doTaskOne();
return new AsyncResult<>("任务一完成");
}
@Async
public Future<String> doTaskTwoCallback() throws Exception {
super.doTaskTwo();
return new AsyncResult<>("任务二完成");
}
@Async
public Future<String> doTaskThreeCallback() throws Exception {
super.doTaskThree();
return new AsyncResult<>("任务三完成");
}
}
~~~
* 在**单元测试**用例中,注入`AsyncCallBackTask`对象,并在测试用例中执行`doTaskOneCallback()`,`doTaskTwoCallback()`,`doTaskThreeCallback()`三个方法。循环调用`Future`的`isDone()`方法等待三个**并发任务**执行完成,记录最终执行时间。
~~~
@Autowired
private AsyncCallBackTask asyncCallBackTask;
@Test
public void testAsyncCallbackTask() throws Exception {
long start = currentTimeMillis();
Future<String> task1 = asyncCallBackTask.doTaskOneCallback();
Future<String> task2 = asyncCallBackTask.doTaskTwoCallback();
Future<String> task3 = asyncCallBackTask.doTaskThreeCallback();
// 三个任务都调用完成,退出循环等待
while (!task1.isDone() || !task2.isDone() || !task3.isDone()) {
sleep(1000);
}
long end = currentTimeMillis();
System.out.println("任务全部完成,总耗时:" + (end - start) + "毫秒");
}
~~~
看看都做了哪些改变:
* 在测试用例一开始记录开始时间;
* 在调用三个异步函数的时候,返回Future类型的结果对象;
* 在调用完三个异步函数之后,开启一个循环,根据返回的Future对象来判断三个异步函数是否都结束了。若都结束,就结束循环;若没有都结束,就等1秒后再判断。
* 跳出循环之后,根据结束时间 - 开始时间,计算出三个任务并发执行的总耗时。
执行一下上述的单元测试,可以看到如下结果:
~~~
开始做任务三
开始做任务一
开始做任务二
完成任务二,耗时:2572毫秒
完成任务一,耗时:7333毫秒
完成任务三,耗时:7647毫秒
任务全部完成,总耗时:8013毫秒
~~~
可以看到,通过**异步调用**,让任务一、任务二、任务三**并发执行**,有效的**减少**了程序的**运行总时间**。
- 内容简介
- 第一章 Spring boot 简介
- 1.1 helloworld
- 1.2 提高开发效率工具lombok
- 1.3 IDEA热部署
- 1.4 IDEA常用插件
- 1.5 常用注解
- 第二章 RESTful接口
- 2.1 RESTful风格API
- 2.1.1 spring常用注解开发RESTful接口
- 2.1.2 HTTP协议与Spring参数接收注解
- 2.1.3 Spring请求处理流程注解
- 2.2 JSON数据格式处理
- 2.2.1 Jackson的转换示例代码
- 2.3 针对接口编写测试代码
- 2.3.1 编码接口测试示例代码
- 2.3.2 带severlet容器的接口测试示例代码
- 2.3.3 Mockito测试示例代码
- 2.3.4 Mockito轻量测试
- 2.4 使用swagger2构建API文档
- 2.4.1 swagger2示例代码
- 2.4.2 pom.xml
- 2.5 使用swagger2导出各种格式的接口文档
- 第三章 sping boot配置管理
- 3.1 YAML语法
- 3.2 YAML绑定配置变量的方式
- 3.3 YAML配置属性值校验
- 3.4 YAML加载外部配置文件
- 3.5 SpEL表达式绑定配置项
- 3.6 不同环境下的多配置
- 3.7 配置文件的优先级
- 3.8 配置文件敏感字段加密
- 第四章 连接数据库使用到的框架
- 4.1 spring JDBC
- 4.2 mybatis配置mybatisgenerator自动生成代码
- 4.3 mybatis操作数据库+dozer整合Bean自动加载
- 4.4 spring boot mybatis 规范
- 4.5 spirng 事务与分布式事务
- 4.6 spring mybaits 多数据源(未在git版本中实现)
- 4.7 mybatis+atomikos实现分布式事务(未在git版本中实现)
- 4.8 mybatis踩坑之逆向工程导致的服务无法启动
- 4.9 Mybatis Plus
- 4.9.1.CURD快速入门
- 4.9.2.条件构造器使用与总结
- 4.9.3.自定义SQL
- 4.9.4.表格分页与下拉分页查询
- 4.9.5.ActiveRecord模式
- 4.9.6.主键生成策略
- 4.9.7.MybatisPlus代码生成器
- 4.9.8.逻辑删除
- 4.9.9.字段自动填充
- 4.9.10.多租户解决方案
- 4.9.11.雪花算法与精度丢失
- 第五章 页面展现整合
- 5.1 webjars与静态资源
- 5.2 模板引擎与未来趋势
- 5.3 整合JSP
- 5.4 整合Freemarker
- 5.5 整合Thymeleaf
- 5.6 Thymeleaf基础语法
- 5.7 Thymeleaf内置对象与工具类
- 5.8 Thymeleaf公共片段(标签)和内联JS
- 第六章 生命周期内的拦截、监听
- 6.1 servlet与filter与listener的实现
- 6.1.1 FilterRegistration
- 6.1.2 CustomFilter
- 6.1.3 Customlister
- 6.1.4 FirstServlet
- 6.2 spring拦截器及请求链路说明
- 6.2.1 MyWebMvcConfigurer
- 6.2.2 CustomHandlerInterceptor
- 6.3 自定义事件的发布与监听
- 6.4 应用启动的监听
- 第七章 嵌入式容器的配置与应用
- 7.1 嵌入式的容器配置与调整
- 7.2 切换到jetty&undertow容器
- 7.3 打war包部署到外置tomcat容器
- 第八章 统一全局异常处理
- 8.1 设计一个优秀的异常处理机制
- 8.2 自定义异常和相关数据结构
- 8.3 全局异常处理ExceptionHandler
- 8.3.1 HelloController
- 8.4 服务端数据校验与全局异常处理
- 8.5 AOP实现完美异常处理方案
- 第九章 日志框架与全局日志管理
- 9.1 日志框架的简介与选型
- 9.2 logback日志框架整合使用
- 9.3 log4j2日志框架整合与使用
- 9.4 拦截器实现用户统一访问日志
- 第十章 异步任务与定时任务
- 10.1 实现Async异步任务
- 10.2 为异步任务规划线程池
- 10.3 通过@Scheduled实现定时任务
- 10.4 quartz简单定时任务(内存持久化)
- 10.5 quartz动态定时任务(数据库持久化)
- 番外章节
- 1.windows下安装git
- 1 git的使用
- 2 idea通过git上传代码到github
- 2.maven配置
- 3.idea几个辅助插件
- 4.idea配置数据库
- 5.搭建外网穿透实现外网访问内网项目
- 6.idea设置修改页面自动刷新
- 7.本地tomcat启动乱码
- 8.win10桌面整理,得到一个整洁的桌面
- 9.//TODO的用法
- 10.navicat for mysql 工具激活
- 11.安装redis
- 12.idea修改内存
- 13.IDEA svn配置
- 14.IntelliJ IDEA像Eclipse一样打开多个项目