JavaEE Servlet的异步处理

Servlet3.0对异步处理提供了支持。

阻塞的Servlet

每个请求到达Web应用后,Web应用会为其分配一个线程来专门负责该请求,直到响应发送前,该线程都不会被线程池回收。若有些请求需要长时间处理(比如某些耗时运算或者需要等待某个资源),就会阻塞线程,若这类的请求很多,许多线程都将被长时间占用,对于系统就会产生较大负担,甚至会造成程序的效能瓶颈。

基本上一些需要长时间处理的请求,用户通常也不要求请求后就立即响应。如果可以让这类请求先释放分配给该请求的线程,让Web应用有机会将线程资源分配给其它请求,这样就可以减轻系统负担。而原先释放了所分配线程的请求,其响应将被延后,直到任务完成后再对用户发送响应。

Servlet3.0对异步处理提供了支持。

AsyncContext

在Servlet3.0中,ServletRequest类提供了几个新方法对异步处理进行支持:
- startAsync():利用原来的请求和响应创建一个异步处理上下文;
- startAsync(ServletRequest, ServletResponse):利用特定的请求和响应创建一个异步处理上下文;
- isAsyncSupported():判断当前请求是否支持异步操作;
- isAsyncStarted():判断当前请求是否开始异步处理;
- getAsyncContext():返回异步处理上下文,在异步处理开始后才能被调用,否则会抛出异常。

在调用了startAsync()方法取得AsyncContext对象后,这次的响应将被延后,并释放被分配到的线程。

AsyncContext提供了如下方法:
- getRequest():返回请求对象;
- getResponse():返回响应对象;
- setTimeout(long):设置超时时间;
- addListener(AsyncListener):注册监听器;
- start(Runnable):开始异步处理耗时任务;
- complete():异步处理完成,将向用户发送响应;
- dispatch(String):将请求转发给另一个Servlet或jsp页面。

在调用了complete()方法后,此次异常处理结束,响应将在此时发送给用户。

这里需要注意的是,AsyncContext不是异步输出,而是同步输出,但是会释放服务器端的线程。使用AsyncContext的时候,对于浏览器来说是在同步等待输出的,但是对于服务器端来说,处理此请求的线程并没有卡在那里等待,则是把当前的耗时任务加入一个线程池中处理,而请求线程将被释放。和自己发起一个新线程去处理耗时任务不同的是,服务器端会创建一个线程池去处理那些需要异步处理的请求,而如果每次请求都发起一个线程去处理的话,这就有可能会消耗大量的线程资源。

一个例子

下面是一个简单的异步处理程序:

// AsyncServlet.java

@WebServlet(asyncSupported = true, urlPatterns = { "/async" })
public class AsyncServlet extends HttpServlet {
    private static final long serialVersionUID = 1L;

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        response.setContentType("text/html;charset=GBK");
        PrintWriter out=response.getWriter();
        out.println("<title>async task</title>");
        out.println("进入Servlet:"+new Date()+"<br>");
        out.flush();

        AsyncContext ac=request.startAsync(request, response);
        ac.addListener(new AsyncListener() {
            @Override
            public void onComplete(AsyncEvent event) throws IOException {
                event.getAsyncContext().getResponse().getWriter().println("业务完成:"+new Date());
                event.getAsyncContext().dispatch("/async");
            }

            @Override
            public void onError(AsyncEvent event) throws IOException {
                event.getAsyncContext().getResponse().getWriter().println("业务错误:"+new Date());
            }

            @Override
            public void onStartAsync(AsyncEvent event) throws IOException {
                event.getAsyncContext().getResponse().getWriter().println("业务开始:"+new Date());
            }

            @Override
            public void onTimeout(AsyncEvent event) throws IOException {
                event.getAsyncContext().getResponse().getWriter().println("业务超时:"+new Date());
            }
        });
        ac.setTimeout(30*1000);
        ac.start(new Task(ac));

        out.println("离开Servlet:"+new Date()+"<br>");
        out.flush();
    }

    public static class Task implements Runnable {
        private AsyncContext ac;

        public Task(AsyncContext a) {
            ac=a;
        }

        @Override
        public void run() {
            try {
                TimeUnit.SECONDS.sleep(5);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            ac.complete();
        }

    }
}

在这个Servlet中,我们调用startAsync(request, response)方法获得了一个AsyncContext对象,并设置超时时间,注册AsyncListener监听器后开始异步执行任务。此任务阻塞5秒来模拟耗时操作,然后调用complete方法结束此异步处理。

AsyncListener可以对异步处理进行监听:
- onComplete:异步处理完成后将回调此方法;
- onError:异步处理出错后将回调此方法;
- onStartAsync:异步处理开始后将回调此方法;
- onTimeout:异步处理超时后将回调此方法。

另外,还需要使用@WebServlet注解的asyncSupported属性对此Servlet进行配置。如果存在Filter作用于此Servlet,则这些Filter也需要设置asyncSupported属性为true

运行此Web应用,在浏览器地址栏输入:http://localhost:8080/WebDemo/async,大约等待5秒后会出现响应页面:

进入ServletSun Feb 05 17:54:41 CST 2017
离开ServletSun Feb 05 17:54:41 CST 2017
业务完成:Sun Feb 05 17:54:46 CST 2017 

可以看到,现在这个Servlet不会被耗时任务所阻塞了。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值