`

spring mvc中的@async

 
阅读更多
引用

1、同步调用
下面通过一个简单示例来直观的理解什么是同步调用:

定义Task类,创建三个处理函数分别模拟三个执行任务的操作,操作消耗时间随机取(10秒内)

@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) + "毫秒");
    }

}
在单元测试用例中,注入Task对象,并在测试用例中执行 doTaskOne 、 doTaskTwo 、 doTaskThree 三个函数。

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:spring-basic.xml")
@ActiveProfiles("dev")
public class ApplicationTests {

    @Autowired
    private Task task;

    @Test
    public void test() throws Exception {
        task.doTaskOne();
        task.doTaskTwo();
        task.doTaskThree();
    }

}
执行单元测试,可以看到类似如下输出:

开始做任务一
完成任务一,耗时:4256毫秒
开始做任务二
完成任务二,耗时:4957毫秒
开始做任务三
完成任务三,耗时:7173毫秒
任务一、任务二、任务三顺序的执行完了,换言之 doTaskOne 、 doTaskTwo 、 doTaskThree 三个函数顺序的执行完成。

2、异步调用
上述的同步调用虽然顺利的执行完了三个任务,但是可以看到执行时间比较长,若这三个任务本身之间不存在依赖关系,可以并发执行的话,同步调用在执行效率方面就比较差,可以考虑通过异步调用的方式来并发执行。

在Spring中,我们只需要通过使用 @Async 注解就能简单的将原来的同步函数变为异步函数,Task类改在为如下模式:

@Component
public class Task {
    @Async
    public void doTaskOne() throws Exception {
        // 同上内容,省略
    }

    @Async
    public void doTaskTwo() throws Exception {
        // 同上内容,省略
    }

    @Async
    public void doTaskThree() throws Exception {
        // 同上内容,省略
    }

}
此时可以反复执行单元测试,您可能会遇到各种不同的结果,比如:

没有任何任务相关的输出
有部分任务相关的输出
乱序的任务相关的输出
原因是目前 doTaskOne 、 doTaskTwo 、 doTaskThree 三个函数的时候已经是异步执行了。主程序在异步调用之后,主程序并不会理会这三个函数是否执行完成了,由于没有其他需要执行的内容,所以程序就自动结束了,导致了不完整或是没有输出任务相关内容的情况。

注: @Async所修饰的函数不要定义为static类型 or 非public,这样异步调用不会生效

3、异步回调
为了让 doTaskOne 、 doTaskTwo 、 doTaskThree 能正常结束,假设我们需要统计一下三个任务并发执行共耗时多少,这就需要等到上述三个函数都完成调动之后记录时间,并计算结果。

那么我们如何判断上述三个异步调用是否已经执行完成呢?我们需要使用 Future 来返回异步调用的结果,就像如下方式改造 doTaskOne 函数:

@Component
public class Task {

    public static Random random =new Random();

    @Async
    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<>("任务一完成");
    }

    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<>("任务二完成");
    }

    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<>("任务三完成");
    }

}
下面我们改造一下测试用例,让测试在等待完成三个异步调用之后来做一些其他事情。

@Test
public void test() throws Exception {

    long start = System.currentTimeMillis();

    Future<String> task1 = task.doTaskOne();
    Future<String> task2 = task.doTaskTwo();
    Future<String> task3 = task.doTaskThree();

    while(true) {
        if(task1.isDone() && task2.isDone() && task3.isDone()) {
            // 三个任务都调用完成,退出循环等待
            break;
        }
        Thread.sleep(1000);
    }

    long end = System.currentTimeMillis();

    System.out.println("任务全部完成,总耗时:" + (end - start) + "毫秒");

}
看看我们做了哪些改变:

在测试用例一开始记录开始时间
在调用三个异步函数的时候,返回 Future 类型的结果对象
在调用完三个异步函数之后,开启一个循环,根据返回的 Future
对象来判断三个异步函数是否都结束了。若都结束,就结束循环;若没有都结束,就等1秒后再判断。
跳出循环之后,根据结束时间 - 开始时间,计算出三个任务并发执行的总耗时。
执行一下上述的单元测试,可以看到如下结果:

开始做任务一
开始做任务二
开始做任务三
完成任务三,耗时:37毫秒
完成任务二,耗时:3661毫秒
完成任务一,耗时:7149毫秒
任务全部完成,总耗时:8025毫秒



注解的应用范围:
类:表示这个类中的所有方法都是异步的
方法:表示这个方法是异步的,如果类也注解了,则以这个方法的注解为准

相关的配置:
<task:annotation-driven />配置:
executor:指定一个缺省的executor给@Async使用。
例子:
<task:annotation-driven executor="asyncExecutor" />

<task:executor />配置参数:
id:当配置多个executor时,被@Async("id")指定使用;也被作为线程名的前缀。
pool-size:
core size:最小的线程数,缺省:1
max size:最大的线程数,缺省:Integer.MAX_VALUE
queue-capacity:当最小的线程数已经被占用满后,新的任务会被放进queue里面,当这个queue的capacity也被占满之后,pool里面会创建新线程处理这个任务,直到总线程数达到了max size,这时系统会拒绝这个任务并抛出TaskRejectedException异常(缺省配置的情况下,可以通过rejection-policy来决定如何处理这种情况)。缺省值为:Integer.MAX_VALUE
keep-alive:超过core size的那些线程,任务完成后,再经过这个时长(秒)会被结束掉
rejection-policy:当pool已经达到max size的时候,如何处理新任务
ABORT(缺省):抛出TaskRejectedException异常,然后不执行
DISCARD:不执行,也不抛出异常
DISCARD_OLDEST:丢弃queue中最旧的那个任务
CALLER_RUNS:不在新线程中执行任务,而是有调用者所在的线程来执行

配置例子:
<task:annotation-driven executor="asyncExecutor" />
<task:executor id="asyncExecutor" pool-size=" 100- 10000" queue-capacity=" 10"/>

实例:
<!-- 缺省的异步任务线程池 -->
<task:annotation-driven executor="asyncExecutor" />
<task:executor id="asyncExecutor" pool-size="100-10000" queue-capacity="10" />

<!-- 处理log的线程池 -->
<task:executor id="logExecutor" pool-size="15-1000" queue-capacity="5" keep-alive="5"/>

@Override
@Async("logExecutor")    //如果不指定名字,会使用缺省的“asyncExecutor”
public void saveUserOpLog(TabUserOpLog tabUserOpLog) {

  userOpLogDAO.insertTabUserOpLog(tabUserOpLog);
}
分享到:
评论

相关推荐

    看透springMvc源代码分析与实践

    22.2.1 Spring MVC中异步请求相关组件286 22.2.2 Spring MVC对异步请求的支持297 22.2.3 WebAsyncTask和Callable类型异步请求的处理过程及用法301 22.2.4 DeferredResult类型异步请求的处理过程及用法303 22.2.5...

    Spring Boot 2 Recipes: A Problem-Solution Approach

    Chapter 4: Spring MVC - Async Chapter 5: Spring WebFlux Chapter 6: Spring Security Chapter 7: Data Access Chapter 8: Java Enterprise Services Chapter 9: Messaging Chapter 10: Spring Boot Actuator ...

    spring-boot-features-demo:Spring Boot太棒了!

    以下是目前本工程展示的所有Spring Boot特性列表:Spring框架Thymeleaf集成基于AOP的分页组件Spring Security集成@Async支持@Retry支持Jackson定制数据绑定(包含类型转换,格式化,校验)Mock测试(Mo

    Spring MVC+ajax进行信息验证的方法

    本文是一个ajax结合Spring MVC使用的入门,首先我们来了解一下什么是Ajax AJAX 不是新的编程语言,而是一种使用现有标准的新方法。AJAX 最大的优点是在不重新加载整个页面的情况下,可以与服务器交换数据并更新部分...

    spring-framework-reference-4.1.2

    3. New Features and Enhancements in Spring Framework 4.0 ............................................ 17 3.1. Improved Getting Started Experience .........................................................

    spring-framework-reference4.1.4

    3. New Features and Enhancements in Spring Framework 4.0 ............................................ 17 3.1. Improved Getting Started Experience .........................................................

    解决Ajax加载JSon数据中文乱码问题

    使用zTree的异步刷新父级菜单时,服务器返回中文乱码,但项目中使用了SpringMvc,已经对中文乱码处理,为什么还会出现呢? 此处为的异步请求的配置: Java代码  async: { enable: true, url: basePath + '/...

    组织者:用于组织化Web服务器的有机Web框架

    它的组织是由于其众所周知的语法,例如在Spring Boot中使用的,它是有机的,因为Organizer很有意义:这就像用几乎自然的语言告诉服务器应该做什么。 Organizer通过反转控制原理来工作,例如在Node.js中为MVC开发...

    阿里云javasdk源码-toutiao:Springboot搭建的资讯网站

    使用Spring boot编写的咨询网站 采用MVC模式,并实现前后端分离 通过myBatis和MySQL实现数据库部分功能 接入七牛云SDK实现图片存储 使用Git进行项目管理 目录结构 ├── README.md // help ├── pom.xml ...

    springMvc 前端用json的方式向后台传递对象数组方法

    如下所示: JSP var vipFee= new Array; //遍历选中的对象 $("#feeList :checkbox:checked").each(function(i){ vipFee.push({"enterpriseSeq":$(this).attr("enterpriseSeq"),"merchNo":... async:false, dataType:

    js+springMVC 提交数组数据到后台的实例

    1.ajax 代码 var ids =new Array(); $.ajax({ type: "POST", ... async: true, success: function (data) { if(data.code==200){ console.log("正在打包...") } } }); 2.后台接收 @RequestMappin

Global site tag (gtag.js) - Google Analytics