项目需求:
以下转自:http://13shu.iteye.com/blog/2021652
目前系统中,有个别的查询比较慢,大概需要几秒才能返回结果。
用户查询开始到返回结果到页面,此处是一个同步的过程,如果做成异步的能提高系统响应的性能。
最近发现servlet3.0有一个新的特性,新增HTTP请求的异步处理,详细请参考。
由于项目采用的SpringMVC做的,所以查看了下SpringMVC的资料,发现3.2版本对于异步处理有良好的封装。
本文使用的版本为Spring 3.2.x,jdk 6
废话不说,上源码
文件结构
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.
测试成功
以下引自Sping3.2.x文档
The Spring Framework provides abstractions for asynchronous execution and scheduling of tasks with the
TaskExecutor
andTaskScheduler
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 |
官方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));
}
}
}
首先,为了以注解方式使用异步功能,我们需要在Spring的xml配置中定义相关的bean:
引自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.
引自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>
<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 theDiscardPolicy
orDiscardOldestPolicy
may be configured instead. Another option that works well for applications that need to throttle the submitted tasks under heavy load is theCallerRunsPolicy
. 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 | 通知该线程的创建者,让其不要提交新的线程 |
@Async
void doSomething(String s) { //可以带参数!
// this will be executed asynchronously
}
Spring的异步是基于方法而不是类!
Spring的异步是基于方法而不是类!
Spring的异步是基于方法而不是类!
说实话,鄙人认为基于方法是Spring的最大优点。负责Http的拦截器@RequestMapping(“”)是基于方法的,异步@Async也是基于方法的。不过也有两个约束:
@Component
class A{
@Autowired
B b;
public void run(){
b.doSomething();
}
}
@Component
class B{
@Async
public void doSomething(){
}
}
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();
}
}