异步servlet

Tomcat分配的servlet线程是非常消耗资源的,如果处理十分复杂的逻辑业务会导致系统分配servlet线程迟迟不能回归到tomcat线程队列之中,造成性能下降,异步servlet的主要目的是为了让tomcat分配的工作线程早点回收,而相应可以延后返回,因此采用异步servlet并没有加快响应的返回速度,只是让tomcat可以同时处理更多的请求。  

按照传统的servlet的走法应该如下图所示

 服务器启动时(web.xml中配置load-on-startup=1,默认为0)或者第一次请求该servlet时,就会初始化一个Servlet对象,也就是会执行初始化方法init(ServletConfig conf)

 该servlet对象去处理所有客户端请求,在service(ServletRequest req,ServletResponse res)方法中执行

 最后服务器关闭时,才会销毁这个servlet对象,执行destroy()方法。

   所以我们可以知道Servlet容器从头到尾只有一个servlet单实例的对象。

Servlet容器默认是采用单实例多线程的方式处理多个请求的:
 

      容器收到一个Servlet请求,调度线程从线程池中选出一个工作者线程,将请求传递给该工作者线程,然后由该线程来执行Servlet的service方法。当这个线程正在执行的时候,容器收到另外一个请求,调度线程同样从线程池中选出另一个工作者线程来服务新的请求,
容器并不关心这个请求是否访问的是同一个Servlet.当容器同时收到对同一个Servlet的多个请求的时候,那么这个Servlet的service()方法将在多线程中并发执行。 

异步情况下的求情处理流程是这样的,首先还是会有请求到来,容器开线程来执行service方法,不过我们可以把业务逻辑另外开一个线程来解决,让容器分配的线程早点回到线程队列之中,我们的响应稍后在返回给客户端。

 

下面展示一下代码 抄自https://blog.csdn.net/wangxindong11/article/details/78591396

package com.test;

import javax.servlet.AsyncContext;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.concurrent.ThreadPoolExecutor;

/**
 * asyncSupported=true表示该servlet支持异步
 */
@WebServlet(urlPatterns = "/AsyncLongRunningServlet", asyncSupported = true)
public class AsyncLongRunningServlet extends HttpServlet {
    private static final long serialVersionUID = 1L;

    @Override
    protected void doGet(HttpServletRequest request,
                         HttpServletResponse response) throws ServletException, IOException {
        long startTime = System.currentTimeMillis();
        System.out.println("AsyncLongRunningServlet Start::Name="
         + Thread.currentThread().getName() + "::ID="
         + Thread.currentThread().getId());
        AsyncContext asyncCtx = request.startAsync();
        asyncCtx.addListener(new AppAsyncListener());
        /*异步servlet的超时时间,异步Servlet有对应的超时时间,
        * 如果在指定的时间内没有执行完操作,response依然会走原来Servlet的结束逻辑
         *,后续的异步操作执行完再写回的时候,可能会遇到异常。*/
        asyncCtx.setTimeout(9000);

        ThreadPoolExecutor executor = (ThreadPoolExecutor) request
         .getServletContext().getAttribute("executor");

        executor.execute(new AsyncRequestProcessor(asyncCtx, 1000));
        long endTime = System.currentTimeMillis();
        System.out.println("AsyncLongRunningServlet End::Name="
         + Thread.currentThread().getName() + "::ID="
         + Thread.currentThread().getId() + "::Time Taken="
         + (endTime - startTime) + " ms.");
    }
}
package com.test;

import javax.servlet.AsyncContext;
import java.io.IOException;
import java.io.PrintWriter;

/**
 * 业务工作线程
 */
public class AsyncRequestProcessor implements Runnable {
    private AsyncContext asyncContext;
    private int secs;

    public AsyncRequestProcessor() {
    }

    public AsyncRequestProcessor(AsyncContext asyncCtx, int secs) {
        this.asyncContext = asyncCtx;
        this.secs = secs;
    }

    @Override
    public void run() {
        System.out.println("Async Supported? "
         + asyncContext.getRequest().isAsyncSupported());
        try {
            PrintWriter out = asyncContext.getResponse().getWriter();
            Thread.sleep(secs);
            out.write("Processing done for " + secs + " milliseconds!!");
        } catch (IOException e) {
            e.printStackTrace();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        asyncContext.complete();
    }


}
package com.test;

import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.annotation.WebListener;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
 * Created by wangxindong on 2017/10/19.
 * 在监听中初始化线程池
 */
@WebListener
public class AppContextListener implements ServletContextListener {
    /**
     * 当Servlet 容器启动Web 应用时调用该方法。在调用完该方法之后,容器再对Filter 初始化,
     * 并且对那些在Web 应用启动时就需要被初始化的Servlet 进行初始化。
     */
    @Override
    public void contextInitialized(ServletContextEvent servletContextEvent) {
        // create the thread pool
        ThreadPoolExecutor executor = new ThreadPoolExecutor(100, 200, 50000L,
         TimeUnit.MILLISECONDS, new ArrayBlockingQueue<Runnable>(100));
        servletContextEvent.getServletContext().setAttribute("executor",
         executor);

    }

    /**
     * 当Servlet 容器终止Web 应用时调用该方法。在调用该方法之前,容器会先销毁所有的Servlet 和Filter 过滤器。
      */
    @Override
    public void contextDestroyed(ServletContextEvent servletContextEvent) {
        ThreadPoolExecutor executor = (ThreadPoolExecutor) servletContextEvent
         .getServletContext().getAttribute("executor");
        executor.shutdown();
    }
}
package com.test;

import javax.servlet.AsyncEvent;
import javax.servlet.AsyncListener;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebListener;
import java.io.IOException;
import java.io.PrintWriter;

/**
 * 1. 异步线程开始时,调用AsyncListener的onStartAsync(AsyncEvent event)方法;
 * 2. 异步线程出错时,调用AsyncListener的onError(AsyncEvent event)方法;
 * 3. 异步线程执行超时,则调用AsyncListener的onTimeout(AsyncEvent event)方法;
 * 4. 异步执行完毕时,调用AsyncListener的onComplete(AsyncEvent event)方法;
 */
@WebListener
public class AppAsyncListener implements AsyncListener {
    //异步执行完毕时,调用AsyncListener的onComplete(AsyncEvent event)方法
    @Override
    public void onComplete(AsyncEvent asyncEvent) throws IOException {
        System.out.println("AppAsyncListener onComplete");
        // we can do resource cleanup activity here
    }

    @Override
    public void onError(AsyncEvent asyncEvent) throws IOException {
        System.out.println("AppAsyncListener onError");
        //we can return error response to client
    }

    @Override
    public void onStartAsync(AsyncEvent asyncEvent) throws IOException {
        System.out.println("AppAsyncListener onStartAsync");
        //we can log the event here
    }

    @Override
    public void onTimeout(AsyncEvent asyncEvent) throws IOException {
        System.out.println("AppAsyncListener onTimeout");
        //we can send appropriate response to client
        ServletResponse response = asyncEvent.getAsyncContext().getResponse();
        PrintWriter out = response.getWriter();
        out.write("TimeOut Error in Processing");
    }
}

上面的代码就是模拟了一下业务时间较长的情况下的返回响应过程。我们在自己写的线程之中睡眠了一会儿。写了两个监听器看看什么情况。

发送请求我们也确实看到了响应。控制台也有了输出

那这样就确定了 AsyncContext 对象之中包含了这次请求和响应对象。并且工作线程在7ms的时间内回归到了队列中。

我们发现所谓的asyncContext实际作用就是传递了response那我们直接利用service中的response开启线程是否能行。

package com.test;

import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;

public class RequestProcessor implements Runnable {

    private HttpServletResponse response;

    public RequestProcessor(HttpServletResponse response) {
        this.response = response;
    }

    @Override
    public void run() {
        PrintWriter out = null;
        try {
            out = response.getWriter();
            Thread.sleep(1000);
            out.write("Processing done for " + 1000 + " milliseconds!!");
        } catch (IOException e) {
            e.printStackTrace();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

    }
}

在servlet中注释掉和异步有关的代码,然后启动我们的这个线程

executor.execute(new RequestProcessor(response));

我们发现是connector组件出错了。网上查了一下说是封装出错了。

接下来我又尝试了同步情况下将response放在其他类里面写是没有任何问题的。看来问题就在于线程之间的变量传递上,因为再往response流里面写入一些字符时,工作线程已经返回队列了,因此会出现问题。

具体的源码和执行流程我百度也没找到,看了下AsyncContext的源代码,发现有个内部类

以下纯属推测,并没有验证,以后会完善。

 private static class AsyncRunnable implements Runnable {
        private final AsyncDispatcher applicationDispatcher;
        private final Request request;
        private final ServletRequest servletRequest;
        private final ServletResponse servletResponse;

        public AsyncRunnable(Request request, AsyncDispatcher applicationDispatcher, ServletRequest servletRequest, ServletResponse servletResponse) {
            this.request = request;
            this.applicationDispatcher = applicationDispatcher;
            this.servletRequest = servletRequest;
            this.servletResponse = servletResponse;
        }

        public void run() {
            this.request.getCoyoteRequest().action(ActionCode.ASYNC_DISPATCHED, (Object)null);

            try {
                this.applicationDispatcher.dispatch(this.servletRequest, this.servletResponse);
            } catch (Exception var2) {
                throw new RuntimeException(var2);
            }
        }
    }

    private static class RunnableWrapper implements Runnable {
        private final Runnable wrapped;
        private final Context context;
        private final org.apache.coyote.Request coyoteRequest;

        public RunnableWrapper(Runnable wrapped, Context ctxt, org.apache.coyote.Request coyoteRequest) {
            this.wrapped = wrapped;
            this.context = ctxt;
            this.coyoteRequest = coyoteRequest;
        }

        public void run() {
            ClassLoader oldCL = this.context.bind(Globals.IS_SECURITY_ENABLED, (ClassLoader)null);

            try {
                this.wrapped.run();
            } finally {
                this.context.unbind(Globals.IS_SECURITY_ENABLED, oldCL);
            }

            this.coyoteRequest.action(ActionCode.DISPATCH_EXECUTE, (Object)null);
        }
    }

从这个线程类推测出我们的异步servlet响应是由tomcat容器中的线程池在继续完成的。所以在不同的线程之间传递request和response还是需要用到servlet提供的AsyncContext类来完成的。自己开线程会导致让tomcat工作线程无法识别这个响应,最后导致connector的封装失败。

当然这都是推测,以后会仔细研究一下,如果有错误请您指出,感谢您的阅读。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值