全网整合营销服务商

电脑端+手机端+微信端=数据同步管理

免费咨询热线:400-708-3566

详解Servlet 3.0/3.1 中的异步处理

在Servlet 3.0之前,Servlet采用Thread-Per-Request的方式处理请求,即每一次Http请求都由某一个线程从头到尾负责处理。如果一个请求需要进行IO操作,比如访问数据库、调用第三方服务接口等,那么其所对应的线程将同步地等待IO操作完成, 而IO操作是非常慢的,所以此时的线程并不能及时地释放回线程池以供后续使用,在并发量越来越大的情况下,这将带来严重的性能问题。即便是像Spring、Struts这样的高层框架也脱离不了这样的桎梏,因为他们都是建立在Servlet之上的。为了解决这样的问题,Servlet 3.0引入了异步处理,然后在Servlet 3.1中又引入了非阻塞IO来进一步增强异步处理的性能。

本文源代码:https://github.com/davenkin/servlet-3-async-learning

项目下载地址:servlet-3-async-learning_jb51.rar

在Servlet 3.0中,我们可以从HttpServletRequest对象中获得一个AsyncContext对象,该对象构成了异步处理的上下文,Request和Response对象都可从中获取。AsyncContext可以从当前线程传给另外的线程,并在新的线程中完成对请求的处理并返回结果给客户端,初始线程便可以还回给容器线程池以处理更多的请求。如此,通过将请求从一个线程传给另一个线程处理的过程便构成了Servlet 3.0中的异步处理。

举个例子,对于一个需要完成长时处理的Servlet来说,其实现通常为:

@WebServlet("/syncHello")
public class SyncHelloServlet extends HttpServlet {

  protected void doGet(HttpServletRequest request,
             HttpServletResponse response) throws ServletException, IOException {
    new LongRunningProcess().run();
    response.getWriter().write("Hello World!");
  }
}

为了模拟长时处理过程,我们创建了一个LongRunningProcess类,其run()方法将随机地等待2秒之内的一个时间:

public class LongRunningProcess {

  public void run() {
    try {

      int millis = ThreadLocalRandom.current().nextInt(2000);
      String currentThread = Thread.currentThread().getName();
      System.out.println(currentThread + " sleep for " + millis + " milliseconds.");
      Thread.sleep(millis);

    } catch (InterruptedException e) {
      e.printStackTrace();
    }
  }
}

此时的SyncHelloServlet将顺序地先执行LongRunningProcess的run()方法,然后将将HelloWorld返回给客户端,这是一个典型的同步过程。

在Servlet 3.0中,我们可以这么写来达到异步处理:

@WebServlet(value = "/simpleAsync", asyncSupported = true)
public class SimpleAsyncHelloServlet extends HttpServlet {

  protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    AsyncContext asyncContext = request.startAsync();

    asyncContext.start(() -> {
      new LongRunningProcess().run();
      try {
        asyncContext.getResponse().getWriter().write("Hello World!");
      } catch (IOException e) {
        e.printStackTrace();
      }
      asyncContext.complete();
    });

  }

此时,我们先通过request.startAsync()获取到该请求对应的AsyncContext,然后调用AsyncContext的start()方法进行异步处理,处理完毕后需要调用complete()方法告知Servlet容器。start()方法会向Servlet容器另外申请一个新的线程(可以是从Servlet容器中已有的主线程池获取,也可以另外维护一个线程池,不同容器实现可能不一样),然后在这个新的线程中继续处理请求,而原先的线程将被回收到主线程池中。事实上,这种方式对性能的改进不大,因为如果新的线程和初始线程共享同一个线程池的话,相当于闲置下了一个线程,但同时又占用了另一个线程。

当然,除了调用AsyncContext的start()方法,我们还可以通过手动创建线程的方式来实现异步处理:

@WebServlet(value = "/newThreadAsync", asyncSupported = true)
public class NewThreadAsyncHelloServlet extends HttpServlet {

  protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

    AsyncContext asyncContext = request.startAsync();

    Runnable runnable = () -> {
      new LongRunningProcess().run();
      try {
        asyncContext.getResponse().getWriter().write("Hello World!");
      } catch (IOException e) {
        e.printStackTrace();
      }
      asyncContext.complete();
    };

    new Thread(runnable).start();

  }

}

自己手动创建新线程一般是不被鼓励的,并且此时线程不能重用。因此,一种更好的办法是我们自己维护一个线程池。这个线程池不同于Servlet容器的主线程池,如下图:

在上图中,用户发起的请求首先交由Servlet容器主线程池中的线程处理,在该线程中,我们获取到AsyncContext,然后将其交给异步处理线程池。可以通过Java提供的Executor框架来创建线程池:

@WebServlet(value = "/threadPoolAsync", asyncSupported = true)
public class ThreadPoolAsyncHelloServlet extends HttpServlet {

  private static ThreadPoolExecutor executor = new ThreadPoolExecutor(100, 200, 50000L, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<>(100));

  protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

    AsyncContext asyncContext = request.startAsync();

    executor.execute(() -> {

      new LongRunningProcess().run();

      try {
        asyncContext.getResponse().getWriter().write("Hello World!");
      } catch (IOException e) {
        e.printStackTrace();
      }

      asyncContext.complete();

    });
  }

}

Servlet 3.0对请求的处理虽然是异步的,但是对InputStream和OutputStream的IO操作却依然是阻塞的,对于数据量大的请求体或者返回体,阻塞IO也将导致不必要的等待。因此在Servlet 3.1中引入了非阻塞IO(参考下图红框内容),通过在HttpServletRequest和HttpServletResponse中分别添加ReadListener和WriterListener方式,只有在IO数据满足一定条件时(比如数据准备好时),才进行后续的操作。

对应的代码示:

@WebServlet(value = "/nonBlockingThreadPoolAsync", asyncSupported = true)
public class NonBlockingAsyncHelloServlet extends HttpServlet {

  private static ThreadPoolExecutor executor = new ThreadPoolExecutor(100, 200, 50000L, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<>(100));

  protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

    AsyncContext asyncContext = request.startAsync();

    ServletInputStream inputStream = request.getInputStream();

    inputStream.setReadListener(new ReadListener() {
      @Override
      public void onDataAvailable() throws IOException {

      }

      @Override
      public void onAllDataRead() throws IOException {
        executor.execute(() -> {
          new LongRunningProcess().run();

          try {
            asyncContext.getResponse().getWriter().write("Hello World!");
          } catch (IOException e) {
            e.printStackTrace();
          }

          asyncContext.complete();

        });
      }

      @Override
      public void onError(Throwable t) {
        asyncContext.complete();
      }
    });


  }

}

在上例中,我们为ServletInputStream添加了一个ReadListener,并在ReadListener的onAllDataRead()方法中完成了长时处理过程。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。


# servlet  # 3.0异步处理  # servlet异步处理  # servlet3  # 异步  # Tomcat怎么实现异步Servlet  # java基于servlet的文件异步上传  # Jquery+ajax+JAVA(servlet)实现下拉菜单异步取值  # 并在  # 我们可以  # 可以通过  # 在上  # 引入了  # 池中  # 都是  # 客户端  # 在这个  # 构成了  # 下载地址  # 下了  # 是从  # 将其  # 这是一个  # 用了  # 也将  # 便可  # 将被  # 因为他们 


相关文章: PHP 500报错的快速解决方法  子杰智能建站系统|零代码开发与AI生成SEO优化指南  红河网站制作公司,红河事业单位身份证如何上传?  ,网站推广常用方法?  深圳网站制作费用多少钱,读秀,深圳文献港这样的网站很多只提供网上试读,但有些人只要提供试读的文章就能全篇下载,这个是怎么弄的?  制作销售网站教学视频,销售网站有哪些?  开源网站制作软件,开源网站什么意思?  制作农业网站的软件,比较好的农业网站推荐一下?  常州自助建站工具推荐:低成本搭建与模板选择技巧  南阳网站制作公司推荐,小学电子版试卷去哪里找资源好?  建站之星IIS配置教程:代码生成技巧与站点搭建指南  如何在阿里云香港服务器快速搭建网站?  如何通过NAT技术实现内网高效建站?  怀化网站制作公司,怀化新生儿上户网上办理流程?  如何在万网ECS上快速搭建专属网站?  如何在Windows环境下新建FTP站点并设置权限?  常州自助建站费用包含哪些项目?  如何在Golang中指定模块版本_使用go.mod控制版本号  东莞专业制作网站的公司,东莞大学生网的网址是什么?  制作网站的软件下载免费,今日头条开宝箱老是需要下载怎么回事?  正规网站制作公司有哪些,目前国内哪家网页网站制作设计公司比较专业靠谱?口碑好?  如何确认建站备案号应放置的具体位置?  定制建站价位费用解析与套餐推荐全攻略  建站之星伪静态规则如何正确配置?  个人网站制作流程图片大全,个人网站如何注销?  C++中引用和指针有什么区别?(代码说明)  深圳网站制作的公司有哪些,dido官方网站?  道歉网站制作流程,世纪佳缘致歉小吴事件,相亲网站身份信息伪造该如何稽查?  枣阳网站制作,阳新火车站打的到仙岛湖多少钱?  建站之星下载版如何获取与安装?  番禺网站制作公司哪家值得合作,番禺图书馆新馆开放了吗?  西安制作网站公司有哪些,西安货运司机用的最多的app或者网站是什么?  如何零基础在云服务器搭建WordPress站点?  建站主机是什么?如何选择适合的建站主机?  高性价比服务器租赁——企业级配置与24小时运维服务  ,在苏州找工作,上哪个网站比较好?  名字制作网站免费,所有小说网站的名字?  c# F# 的 MailboxProcessor 和 C# 的 Actor 模型  潮流网站制作头像软件下载,适合母子的网名有哪些?  网站制作多少钱一个,建一个论坛网站大约需要多少钱?  建站之家VIP精选网站模板与SEO优化教程整合指南  七夕网站制作视频,七夕大促活动怎么报名?  定制建站策划方案_专业建站与网站建设方案一站式指南  头像制作网站在线制作软件,dw网页背景图像怎么设置?  长沙做网站要多少钱,长沙国安网络怎么样?  在线ppt制作网站有哪些,请推荐几个好的课件下载的网站?  h5在线制作网站电脑版下载,h5网页制作软件?  开心动漫网站制作软件下载,十分开心动画为何停播?  c++怎么用jemalloc c++替换默认内存分配器【性能】  ,有什么在线背英语单词效率比较高的网站? 

您的项目需求

*请认真填写需求信息,我们会在24小时内与您取得联系。