第 十四章 异步处理

Servlet 不是单例, 但在同一服务器的同一类请求中只会被创建一次.
Servlet 的实例创建个数与一下条件有关:
1.是否部署在分布式环境中, 非分布式只会创建一个实例
2.是否实现SingleThreadMode 接口, 若实现, 同一 Servlet 实例同一服务器最多创建20个
3.在 web.xml中被声明的次数, 若被声明多次, 则会被多次创建实例

Servlet容器默认是采用单实例多线程的方式处理多个请求的:
1.当web服务器启动的时候(或客户端发送请求到服务器时),Servlet就被加载并实例化(只存在一个Servlet实例);
2.容器初始化化Servlet主要就是读取配置文件(例如tomcat,可以通过servlet.xml的设置线程池中线程数目,初始化线程池通过web.xml,初始化每个参数值等等。
3.当请求到达时,Servlet容器通过调度线程(Dispatchaer Thread) 调度它管理下线程池中等待执行的线程(Worker Thread)给请求者;
4.线程执行Servlet的service方法;
5.请求结束,放回线程池,等待被调用;
注意:避免使用实例变量(成员变量),因为如果存在成员变量,可能发生多线程同时访问该资源时,都来操作它,造成数据的不一致,因此产生线程安全问题)

同一 servlet 同时要处理多个请求, 就需要产生线程, 异步执行, 根据内存考虑, 在 Tomcat7 中, 处理进来请求的最多线程数量为200.
Servlet 或 Filter 一直占用请求处理线程, 直到它完成任务, 如果任务耗时, 并发用户的数量超出线程数量, 容器将会遇到超出线程的风险. Tomcat 会将超出的请求堆放在一个内部的服务器 Socket 中(其它容器可能不同). 如果继续进来更多请求, 会被拒绝访问, 知道有资源处理请求.

如果用户需要在请求时异步处理一个长时间任务, 但用户不必要知道任务执行的结果, 那么直接提供一个 Runnable 给 Executor, 并立即返回即可. 如果需要知道执行状态结果就需要做一些处理了.

一 .编写异步的 Servlet
WebServlet 和 WebFilter 注解类型可以包含 asyncSupport 属性, web.xml 的配置也有 配置, 为了编写支持异步处理的 Servlet 和 Filter ,这个属性必须为 true
支持异步处理的 Servlet 或 Filter 可以通过 ServletRequest 中调用 startAsync 方法来启动新的线程, z这个方法有两个重载的方法

AsyncContext startAsync() throws IllegalStateException;

AsyncContext startAsync(ServletRequest var1, ServletResponse var2) throws 

IllegalStateException;
两个方法都返回 AsyncContext 实例, 第一个方法返回的 AsyncContext 包含原始的 request, response, 第二个方法带有ServletRequest var1, ServletResponse var2 参数, 可以传递原有实例, 也可以传递包装后的 wapper 实例.
注意, 重复调用 startAsync 方法只会返回相同 AsyncContext, 如果在不支持异步的 servlet 中调用 startAsync 方法会抛出IllegalStateException 异常,
注意, AsyncContext的 start 方法不会造成阻塞, 因此, 及时他派发的线程还没启动, 也会继续执行下一行代码.
编写异步 Servlet步骤
在 ServletRequest 中调用 startAsync 方法. startAsync 会返回一个 AsyncContext.
在 AsyncContext 中调用 setTimeout() 方法, 设置一个容器必须等待指定任务完成的毫秒数. 这个步骤是可选的, 但是如果没有社会这个时限, 将会采用容器的默认时间, 如果任务没能在规定实现内完成, 将会抛出异常.
调用 asyncContext.start 方法, 传递一个执行长时间的 Runnable.
任务完成时, 通过 Runnable 调用 AsyncContext.complete 方法 或 AsyncContext,dispatch方法表示任务完成, 终止这个线程, 若不调用, 会根据 setTimeout 的时间停止线程

@Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        AsyncContext asyncContext =  req.startAsync();
        asyncContext.setTimeout(5000);
        req.setAttribute("mainThread", Thread.currentThread().getName());
        asyncContext.start(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                req.setAttribute("asyncThread", Thread.currentThread().getName());
                asyncContext.dispatch("/index.jsp");
            }
        });
//        req.getRequestDispatcher("index.jsp").forward(req, resp);
    }

调用asyncContext.dispatch("/index.jsp”);可返回jsp 页面, 后面不需要写 //        req.getRequestDispatcher(“index.jsp”).forward(req, resp);
如果用 req.getRequestDispatcher(“index.jsp”).forward(req, resp);req返回页面, 页面会重复获取 EL表达式内容, 并且无法获取到线程内存储的信息
这里写图片描述
如果不想在任务完成之时分配给两一个资源, 也可以在 AsyncContext中调用 complete 方法, 这个方法向 Servlet 容器表名, 任务已经完成

异步监听器
对于 Servlet 或 Filter 的异步操作, 有一个一步操作监听器 AsyncListener 接口, 这个接口有四个方法

void onComplete(AsyncEvent var1) throws IOException;    //异步操作完成

void onTimeout(AsyncEvent var1) throws IOException;   //异步操作超时

void onError(AsyncEvent var1) throws IOException;     //异步操作失败

void onStartAsync(AsyncEvent var1) throws IOException; // 线程刚刚启动

AsyncListener 没有注解注册监听, 只能在 AsyncContext 中添加注册 listener 实例 如下

public class TestAsyncServlet extends HttpServlet {
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doGet(req,resp);
    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        AsyncContext asyncContext =  req.startAsync();
        asyncContext.setTimeout(5000);
        asyncContext.addListener(new TestAsyncServletListener());
        req.setAttribute("mainThread", Thread.currentThread().getName());

        asyncContext.start(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                req.setAttribute("asyncThread", Thread.currentThread().getName());
                asyncContext.dispatch("/index.jsp");
            }
        });
        req.getRequestDispatcher("index.jsp").forward(req, resp);

    }
}
public class TestAsyncServletListener implements AsyncListener {
    @Override
    public void onComplete(AsyncEvent asyncEvent) throws IOException {
        System.out.println("完成");
    }

    @Override
    public void onTimeout(AsyncEvent asyncEvent) throws IOException {
        System.out.println("超时");
    }

    @Override
    public void onError(AsyncEvent asyncEvent) throws IOException {
        System.out.println("错误");
    }

    @Override
    public void onStartAsync(AsyncEvent asyncEvent) throws IOException {
        System.out.println("开始");
    }
}

其中要注意的是onStartAsync,由于AsyncContext是在ServletRequest的startSync后才能获取到,获取后才能添加listener,所以第一次的onStartAsync是不会被调用到的,但由于ServletRequest的startSync可以多次调用,所以当下次startSync时,onStartAsync才会被调用到。 (还没搞定, 还是不调用)

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值