項目需求:
如下轉自:http://13shu.iteye.com/blog/2021652java
目前系統中,有個別的查詢比較慢,大概須要幾秒才能返回結果。web
用戶查詢開始到返回結果到頁面,此處是一個同步的過程,若是作成異步的能提升系統響應的性能。spring
最近發現servlet3.0有一個新的特性,新增HTTP請求的異步處理,詳細請參考。spring-mvc
因爲項目採用的SpringMVC作的,因此查看了下SpringMVC的資料,發現3.2版本對於異步處理有良好的封裝。併發
目錄:mvc
本文使用的版本爲spring 3.2.x,jdk 6
廢話不說,上源碼app
文件結構框架
com.your_app |--AsyncMethods +--ClassA
@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異步
<?xml version="1.0" encoding="UTF-8"?> <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 "> <!--掃描項目實例化@Component,@Service,@Controller修飾的類--> <context:component-scan base-package="com.your_app" /> <task:annotation-driven /> <!--容許@Async--> </beans>
輸出:async
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的配置:
<bean id="taskExecutor" class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor"> <property name="corePoolSize" value="5" /> <property name="maxPoolSize" value="10" /> <property name="queueCapacity" value="25" /> </bean> <bean id="taskExecutorExample" class="TaskExecutorExample"> <constructor-arg ref="taskExecutor" /> </bean>
配置解釋
當一個任務經過execute(Runnable)方法欲添加到線程池時:
一、 若是此時線程池中的數量小於corePoolSize,即便線程池中的線程都處於空閒狀態,也要建立新的線程來處理被添加的任務。
二、 若是此時線程池中的數量等於 corePoolSize,可是緩衝隊列 workQueue未滿,那麼任務被放入緩衝隊列。
三、若是此時線程池中的數量大於corePoolSize,緩衝隊列workQueue滿,而且線程池中的數量小於maximumPoolSize,建新的線程來處理被添加的任務。
四、 若是此時線程池中的數量大於corePoolSize,緩衝隊列workQueue滿,而且線程池中的數量等於maximumPoolSize,那麼經過 handler所指定的策略來處理此任務。也就是:處理任務的優先級爲:核心線程corePoolSize、任務隊列workQueue、最大線程 maximumPoolSize,若是三者都滿了,使用handler處理被拒絕的任務。
五、 當線程池中的線程數量大於 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.
因此,若是咱們僅僅添加<task:annotation-driven/>
,也可使用@Async標籤。然而,此時使用的是SimpleAsyncTaskExecutor。如「官方文檔27章:Task Execution」中所述,SimpleAsyncTaskExecutor不會使用線程池,僅僅是爲每個請求新開一個線程。這樣在大併發的業務場景下,發生OutOfMemory是不足爲奇的。
<?xml version="1.0" encoding="UTF-8"?> <!--Spring框架的xml標籤訂義文檔, 可訪問http://www.springframework.org/schema/task/查看最新task組件的xml標籤文檔--> <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"> <!--掃描項目實例化@Component,@Service,@Controller修飾的類--> <context:component-scan base-package="com.your_app" /> <!--create a SimpleAsyncTaskExecutor instance--> <task:annotation-driven/> </beans>
<?xml version="1.0" encoding="UTF-8"?> <!--Spring框架的xml標籤訂義文檔, 可訪問http://www.springframework.org/schema/task/查看最新task組件的xml標籤文檔--> <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"> <!--掃描項目實例化@Component,@Service,@Controller修飾的類--> <context:component-scan base-package="com.your_app" /> <!-- 在代碼中@Async不加參數就會使用task:annotation-driven標籤訂義的executor--> <task:annotation-driven executor="myExecutor"/> <!-- 在代碼中@Async("myExecutor")能夠顯式指定executor爲"myExecutor"--> <task:executor id="myExecutor" pool-size="5-25" queue-capacity="100" rejection-policy="CALLER_RUNS"/> </beans>
其中,注意到屬性pool-size的值」5-25」是一個範圍,這對應的是線程池的min和max容量,它們的意義請參考本文上一節的「配置說明」裏的第三、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(); } }