转自 http://13shu.iteye.com/blog/2021652
servlet3.0有一个新的特性,新增HTTP请求的异步处理,详细请参考。
由于项目采用的SpringMVC做的,所以查看了下SpringMVC的资料,发现3.2版本对于异步处理有良好的封装。
- 配置servlet3.0
web.xml中配置
- <web-app version="3.0"
- xmlns="http://java.sun.com/xml/ns/javaee"
- xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
- http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd">
- <servlet>
- <servlet-name>test</servlet-name>
- <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
- <load-on-startup>2</load-on-startup>
- <strong><async-supported>true</async-supported></strong>
- </servlet>
- <servlet-mapping>
- <servlet-name>test</servlet-name>
- <url-pattern>/</url-pattern>
- <servlet-mapping>
- Spring控制层代码修改返回类型为Callable模型
- public Callable<String> processUpload(HttpServletRequest request,
- final HttpServletResponse response) {
- System.out.println("线程名称:"+Thread.currentThread().getName());
- return new Callable<String>() {
- public String call() throws Exception {
- try {
- System.out.println("线程名称:"+Thread.currentThread().getName());
- response.setContentType("text/plain;charset=utf-8");
- response.getWriter().write("nihao");
- response.getWriter().close();
- } catch (IOException e) {
- e.printStackTrace();
- }
- return null;
- }
- };
- }
控制台输出内容:
13:46:59,875 INFO [stdout] (http-localhost-127.0.0.1-8080-2) 线程名称:http-localhost-127.0.0.1-8080-2
13:48:57,609 INFO [stdout] (abc-2) 线程名称:abc-2
Callable内部与外部输出了当前线程的名字,发现线程被切换了,request请求的线程被释放到了web容器中(如:tomcat)。
问题貌似解决了,离下班时间还早于是乎想看下这个神奇的过程是怎么发生的,看了下Spring的源代码,不看不知道,一看吓一跳。
Spring返回的Callable被RequestMappingHandlerAdapter拦截处理了,结果发现Callable被SimpleAsyncTaskExecutor线程池处理了,经过细心的查看代码发现SimpleAsyncTaskExecutor,每当任务被提交到此“线程池(这里就交线程池了)”时,线程池产生一个新的线程去执行Callable中的代码,每次都产生新的线程而且没有上线(默认没有上线,可以设置concurrencyLimit属性来设置线程数的大小)。
- public void execute(Runnable task, long startTimeout) {
- Assert.notNull(task, "Runnable must not be null");
- if (isThrottleActive() && startTimeout > TIMEOUT_IMMEDIATE) {
- this.concurrencyThrottle.beforeAccess();
- doExecute(new ConcurrencyThrottlingRunnable(task));
- }
- else {
- doExecute(task);
- }
- }
- protected void beforeAccess() {
- if (this.concurrencyLimit == NO_CONCURRENCY) {
- throw new IllegalStateException(
- "Currently no invocations allowed - concurrency limit set to NO_CONCURRENCY");
- }
- if (this.concurrencyLimit > 0) {
- boolean debug = logger.isDebugEnabled();
- synchronized (this.monitor) {
- boolean interrupted = false;
- while (this.concurrencyCount >= this.concurrencyLimit) {
- if (interrupted) {
- throw new IllegalStateException("Thread was interrupted while waiting for invocation access, " +
- "but concurrency limit still does not allow for entering");
- }
- if (debug) {
- logger.debug("Concurrency count " + this.concurrencyCount +
- " has reached limit " + this.concurrencyLimit + " - blocking");
- }
- try {
- this.monitor.wait();
- }
- catch (InterruptedException ex) {
- // Re-interrupt current thread, to allow other threads to react.
- Thread.currentThread().interrupt();
- interrupted = true;
- }
- }
- if (debug) {
- logger.debug("Entering throttle at concurrency count " + this.concurrencyCount);
- }
- this.concurrencyCount++;
- }
- }
- }
Spring中应该有其他线程池来支持此功能。最终选择ThreadPoolTaskExecutor,看起来与ThreadPoolExecutor类似。
- private int corePoolSize = 1;
- private int maxPoolSize = Integer.MAX_VALUE;
- private int keepAliveSeconds = 60;
- private boolean allowCoreThreadTimeOut = false;
- private int queueCapacity = Integer.MAX_VALUE;
- private ThreadPoolExecutor threadPoolExecutor;
默认corePoolSize=1,maxPoolSize、queueCapacity为整型最大值,keepAliveSeconds=60(单位秒)
- <bean id="myThreadPool"
- class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor">
- <property name="corePoolSize" value="5" /><!--最小线程数 -->
- <property name="maxPoolSize" value="10" /><!--最大线程数 -->
- <property name="queueCapacity" value="50" /><!--缓冲队列大小 -->
- <property name="threadNamePrefix" value="abc-" /><!--线程池中产生的线程名字前缀 -->
- <property name="keepAliveSeconds" value="30" /><!--线程池中空闲线程的存活时间单位秒 -->
- </bean>
- <mvc:annotation-driven>
- <mvc:async-support task-executor="myThreadPool"
- default-timeout="600">
- <mvc:callable-interceptors>
- <bean class="com.zhongyu.ABC" />
- </mvc:callable-interceptors>
- </mvc:async-support>
- </mvc:annotation-driven>
采用annotation-driven方式配置线程池,异步操作拦截器
- public <T> Object handleTimeout(NativeWebRequest request, Callable<T> task)
- throws Exception {
- HttpServletResponse servletResponse = request.getNativeResponse(HttpServletResponse.class);
- if (!servletResponse.isCommitted()) {
- servletResponse.setContentType("text/plain;charset=utf-8");
- servletResponse.getWriter().write("超时了");
- servletResponse.getWriter().close();
- }
- return null;
- }
请求设定default-timeout超时后,会调用拦截器中handleTimeout进行逻辑处理
还需要优化的地方:
- 由于ThreadPoolTaskExecutor内部缓冲队列采用的是阻塞队列LinkedBlockingDeque,如果队列满了,外部线程会继续等待,需要设置HTTP请求的超时时间
- 线程池的大小配置