SpringMVC异步处理之@Async(附源代码 - 单元测试通过)

项目需求:
以下转自:http://13shu.iteye.com/blog/2021652

目前系统中,有个别的查询比较慢,大概需要几秒才能返回结果。

用户查询开始到返回结果到页面,此处是一个同步的过程,如果做成异步的能提高系统响应的性能。

最近发现servlet3.0有一个新的特性,新增HTTP请求的异步处理,详细请参考。

由于项目采用的SpringMVC做的,所以查看了下SpringMVC的资料,发现3.2版本对于异步处理有良好的封装。


目录:

  • 一个Async的Demo的源码
    • 测试
  • Async的实现机制
    • 官方文档27章Task Execution
    • 使用ThreadPoolTaskExecutor传统方式
    • 推荐 - 使用ThreadPoolTaskExecutor注解方式
      • 必须在-servletxml而不是applicationContextxml中定义Async相关配置
      • 不使用线程池版本
      • 推荐 - 使用线程池版本
      • Async的修饰对象是方法而不是类
        • 约束一 调用者和被Async修饰的方法必须定义在两个类中
        • 约束二 Async和PostConstruct不能同时在同一个类中使用 分别写在两个类中如下


本文使用的版本为Spring 3.2.x,jdk 6
废话不说,上源码

一个@Async的Demo的源码

文件结构

com.your_app
  |--AsyncMethods
  +--ClassA
@Component //Component Service Controller在Spring 3.2中是等价的
class AsyncMethods{
    @Async //异步标签
    public void testAsyn(){
        long time = System.currentTimeMillis();
        System.err.println(Thread.currentThread().getId());
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.err.println("\nasyn total time:"+(System.currentTimeMillis()-time));
    }
}
@Service //Component Service Controller在Spring 3.2中是等价的
class ClassA{
    @Autowired
    AsyncMethods asyncMethods; // 实例的名字必须和类名完全一样,然后首字母小写

    public testAsync(){
        System.err.println(Thread.currentThread().getId());
        logger.info("enter time:" + System.currentTimeMillis());
        asyncMethods.testAsyn()
        logger.info("leave time:" + System.currentTimeMillis());
    }

}

Spring配置:
WEB-INF/*-servlet.xml


<beans default-autowire="byName"
    xmlns="http://www.springframework.org/schema/beans"    
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:context="http://www.springframework.org/schema/context" 
    xmlns:task="http://www.springframework.org/schema/task"     
    xmlns:mvc="http://www.springframework.org/schema/mvc"
    xsi:schemaLocation="
       http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task.xsd
       http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
       http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd
       ">
    
    <context:component-scan base-package="com.your_app" /> 
    <task:annotation-driven /> 
beans>

测试

输出:

13
enter time: 1465784296073
14
leave time: 1465784296074
asyn total time: 5000

可见调用者线程id为13,仅仅花了1ms
被调用的testAsync()方法线程id为14,花了5000ms.
测试成功

@Async的实现机制

官方文档27章:Task Execution

以下引自Sping3.2.x文档

The Spring Framework provides abstractions for asynchronous execution and scheduling of tasks with theTaskExecutorand TaskScheduler interfaces.

To use Servlet 3 async request processing, you need to update web.xml to version 3.0

web.xml 3.0才支持异步, 关于如何修改web.xml版本到3.0, 请看鄙人的另一篇日志的第一章.

以下是官方已经实现的全部7个TaskExecuter。Spring宣称对于任何场景,这些TaskExecuter完全够用了:

名字 特点
SimpleAsyncTaskExecutor 每次请求新开线程,没有最大线程数设置
SyncTaskExecutor 不是异步的线程
ConcurrentTaskExecutor 少用这个,多用ThreadPoolTaskExecutor
SimpleThreadPoolTaskExecutor 监听Spring’s lifecycle callbacks,并且可以和Quartz的Component兼容
ThreadPoolTaskExecutor 最常用。要求jdk版本大于等于5。可以在程序而不是xml里修改线程池的配置
TimerTaskExecutor
WorkManagerTaskExecutor

使用ThreadPoolTaskExecutor(传统方式)

官方demo:
比起从线程池取一个线程再执行, 你仅仅需要把你的Runnable类加入到队列中,然后TaskExecutor用它内置的规则决定何时开始取一个线程并执行该Runnable类

先在xml中添加bean的配置:

"taskExecutor" class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor">
  "corePoolSize" value="5" />
  "maxPoolSize" value="10" />
  "queueCapacity" value="25" />


"taskExecutorExample" class="TaskExecutorExample">
  "taskExecutor" />

配置解释
当一个任务通过execute(Runnable)方法欲添加到线程池时:
1、 如果此时线程池中的数量小于corePoolSize,即使线程池中的线程都处于空闲状态,也要创建新的线程来处理被添加的任务。
2、 如果此时线程池中的数量等于 corePoolSize,但是缓冲队列 workQueue未满,那么任务被放入缓冲队列。
3、如果此时线程池中的数量大于corePoolSize,缓冲队列workQueue满,并且线程池中的数量小于maximumPoolSize,建新的线程来处理被添加的任务。
4、 如果此时线程池中的数量大于corePoolSize,缓冲队列workQueue满,并且线程池中的数量等于maximumPoolSize,那么通过 handler所指定的策略来处理此任务。也就是:处理任务的优先级为:核心线程corePoolSize、任务队列workQueue、最大线程 maximumPoolSize,如果三者都满了,使用handler处理被拒绝的任务。
5、 当线程池中的线程数量大于 corePoolSize时,如果某线程空闲时间超过keepAliveTime,线程将被终止。这样,线程池可以动态的调整池中的线程数。

具体调用:

import org.springframework.core.task.TaskExecutor;

public class TaskExecutorExample {

  private class MessagePrinterTask implements Runnable {

    private String message;

    public MessagePrinterTask(String message) {
      this.message = message;
    }

    public void run() {
      System.out.println(message);
    }

  }

  private TaskExecutor taskExecutor;

  public TaskExecutorExample(TaskExecutor taskExecutor) {
    this.taskExecutor = taskExecutor;
  }

  public void printMessages() {
    for(int i = 0; i < 25; i++) {
      taskExecutor.execute(new MessagePrinterTask("Message" + i));
    }
  }
}

推荐 - 使用ThreadPoolTaskExecutor(注解方式)

首先,为了以注解方式使用异步功能,我们需要在Spring的xml配置中定义相关的bean:

1 必须在*-servlet.xml而不是applicationContext.xml中定义@Async相关配置

引自http://stackoverflow.com/questions/6610563/spring-async-not-working

In short, the context loaded by the ContextLoaderListener (generally from applicationContext.xml) is the parent of the context loaded by the DispatcherServlet (generally from -servlet.xml). If you have the bean with the @Async method declared/component-scanned in both contexts, the version from the child context (DispatcherServlet) will override the one in the parent context (ContextLoaderListener). I verified this by excluding that component from component scanning in the -servlet.xml – it now works as expected.

2 不使用线程池版本

引自http://www.springframework.org/schema/task/spring-task-3.2.xsd

Specifies the java.util.Executor instance to use when invoking asynchronous methods. If not provided, an instance of org.springframework.core.task.SimpleAsyncTaskExecutor will be used by default. Note that as of Spring 3.1.2, individual @Async methods may qualify which executor to use, meaning that the executor specified here acts as a default for all non-qualified @Async methods.

所以,如果我们仅仅添加,也可以使用@Async标签。然而,此时使用的是SimpleAsyncTaskExecutor。如“官方文档27章:Task Execution”中所述,SimpleAsyncTaskExecutor不会使用线程池,仅仅是为每一个请求新开一个线程。这样在大并发的业务场景下,发生OutOfMemory是不足为奇的。



<beans xmlns:task="http://www.springframework.org/schema/task"
       xsi:schemaLocation="
                http://www.springframework.org/schema/task
                http://www.springframework.org/schema/task/spring-task-3.2.xsd">
    
    <context:component-scan base-package="com.your_app" /> 

    
    <task:annotation-driven/>
beans>

3 推荐 - 使用线程池版本



<beans xmlns:task="http://www.springframework.org/schema/task"
       xsi:schemaLocation="
                http://www.springframework.org/schema/task
                http://www.springframework.org/schema/task/spring-task-3.2.xsd">

    
    <context:component-scan base-package="com.your_app" />

    
    <task:annotation-driven executor="myExecutor"/>
    
    <task:executor id="myExecutor"
               pool-size="5-25"
               queue-capacity="100"
               rejection-policy="CALLER_RUNS"/>
beans>

其中,注意到属性pool-size的值”5-25”是一个范围,这对应的是线程池的min和max容量,它们的意义请参考本文上一节的“配置说明”里的第3、4点。如果只有一个值,如pool-size=n, 意味着minSize==maxSize==n

而关于rejection-policy,官方文档里说:

By default, when a task is rejected, a thread pool executor will throw a TaskRejectedException. However, the rejection policy is actually configurable. The exception is thrown when using the default rejection policy which is the AbortPolicy implementation. For applications where some tasks can be skipped under heavy load, either the DiscardPolicy or DiscardOldestPolicy may be configured instead. Another option that works well for applications that need to throttle the submitted tasks under heavy load is the CallerRunsPolicy. Instead of throwing an exception or discarding tasks, that policy will simply force the thread that is calling the submit method to run the task itself. The idea is that such a caller will be busy while running that task and not able to submit other tasks immediately. Therefore it provides a simple way to throttle the incoming load while maintaining the limits of the thread pool and queue. Typically this allows the executor to “catch up” on the tasks it is handling and thereby frees up some capacity on the queue, in the pool, or both. Any of these options can be chosen from an enumeration of values available for the ‘rejection-policy’ attribute on the ‘executor’ element.

总结如下:

池满时的拒绝策略 效果
AbortPolicy(默认) 抛异常
DiscardPolicy or DiscardOldestPolicy 放弃该线程
CallerRunsPolicy 通知该线程的创建者,让其不要提交新的线程

4 @Async的修饰对象是方法而不是类

@Async
void doSomething(String s) { //可以带参数!
    // this will be executed asynchronously
}

Spring的异步是基于方法而不是类!
Spring的异步是基于方法而不是类!
Spring的异步是基于方法而不是类!

说实话,鄙人认为基于方法是Spring的最大优点。负责Http的拦截器@RequestMapping(“”)是基于方法的,异步@Async也是基于方法的。不过也有两个约束:

约束一 调用者和被@Async修饰的方法必须定义在两个类中

@Component
class A{
    @Autowired
    B b;

    public void run(){
        b.doSomething();
    }
}
@Component
class B{
    @Async
    public void doSomething(){
    }
}

约束二 @Async和@PostConstruct不能同时在同一个类中使用. 分别写在两个类中,如下:

public class SampleBeanImpl implements SampleBean {

  @Async
  void doSomething() { … }
}


public class SampleBeanInititalizer {

  private final SampleBean bean;

  public SampleBeanInitializer(SampleBean bean) {
    this.bean = bean;
  }

  @PostConstruct
  public void initialize() {
    bean.doSomething();
  }
}

你可能感兴趣的:(java,spring,架构师之路)