Servlet只有同步模型是怎样的?

异步处理是Servlet3.0版本的重要功能之一,分析异步处理模型之前,先看看同步处理的过程是怎样的:

  • 客户端发起HTTP请求一个动态Servlet API,请求到达服务器端后经过静态服务器过滤后转交给Servlet容器,
  • 容器从主线程池获取一个线程,开始执行Servlet程序,执行结束后得到了完整的响应内容并将其返回给调用方
  • 然后将该线程还回线程池,整个过程都是由同一个主线程在执行。

上述模型存在什么问题呢?

  • 作为服务器要想提高并发性能,就只能通过提高线程池的最大线程数。即吞吐量瓶颈受线程池约束。
  • 更严重的问题是:Servlet执行期间始终占用了该线程池线程,假如某个高频且耗时的Servlet被请求,那就会占用大量甚至全部的线程池线程,进而导致没有线程来处理其它请求。

什么是异步处理模式?

相对于前面的同步处理,异步处理的核心本质就是提供了一个突破点:

让Servlet程序能够将这些慢操作分配给新线程来执行,同时尽快将该Servlet所占用的线程归还到容器线程池。

先来看看异步模式的Servlet程序是怎么样的:

import javax.servlet.AsyncContext;
import javax.servlet.ServletException;
import javax.servlet.ServletResponse;
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.io.PrintWriter;
import java.util.Date; @WebServlet(urlPatterns = "/asyncapi", asyncSupported = true)
public class AsyncServlet extends HttpServlet {
public void service(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
response.setContentType("text/html;charset=GBK");
PrintWriter printWriter = response.getWriter();
printWriter.println("<title>异步Servlet示例</title>");
printWriter.println("进入Servlet的时间:" + new Date() + "<br/>");
printWriter.println("执行Servlet的线程ID:" + Thread.currentThread().getId() + " Name=" + Thread.currentThread().getName() + "<br/>"); // 创建AsyncContext,开始异步调用
final AsyncContext asyncContext = request.startAsync();
asyncContext.setTimeout(50 * 1000);// 设置异步调用的超时时长,限制HTTP响应的最大耗时
asyncContext.start(
new Runnable() {//创建新线程继续执行
public void run() {
try {
Thread.sleep(1 * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
printWriter.println("执行业务处理的线程ID:" + Thread.currentThread().getId() + " Name=" + Thread.currentThread().getName() + "<br/>");
ServletResponse response = asyncContext.getResponse();
/* ... print to the response ... */
printWriter.println("请求处理结束:" + new Date() + "<br/>");
asyncContext.complete();
}
}
);
printWriter.println("退出Servlet的时间:" + new Date() + "<br/>");
}
}

请求该API,根据返回值可以看到现象:

  • Servlet内部模拟耗时了1秒,postman监测到实际耗时也是1秒
  • 处理该次HTTP请求期间,共使用了两个线程
  • 进入和离开Servlet的时间一致,说明该线程占用很短暂
  • 执行业务处理的耗时操作占用了另外一个线程,且执行完成后才返回HTTP响应

至此,我们通过演示程序确实看到使用异步模式将Servet部分和耗时操作部分分配到了不同的线程执行。那么耗时操作用到的新线程来资源哪里?

异步处理模式的实现原理

根据文档描述,start()方法是启用新线程的关键,它是怎么实现的呢?扒拉源码看看吧

这需要我们查看Servlet容器的具体实现来验证,此处以Tomcat 9.0源码为例进行分析。

下载好源码后,首选找到接口AsyncContext及实现类AsyncContextImpl的源码:

上图看到tomcat的状态机进行调度后续的处理。

我们根据状态值检索到后续入口:

最终看到是通过executor来执行runnable程序,而executor即tomcat中可配置的连接池。

当然也不是只能使用start()来启用新线程,我们甚至可以手工new线程来执行耗时操作,演示如下:

import javax.servlet.AsyncContext;
import javax.servlet.ServletException;
import javax.servlet.ServletResponse;
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.io.PrintWriter;
import java.util.Date; @WebServlet(urlPatterns = "/asyncapi_newthread", asyncSupported = true)
public class AsyncServlet_NewThread extends HttpServlet {
public void service(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
response.setContentType("text/html;charset=GBK");
PrintWriter printWriter = response.getWriter();
printWriter.println("<title>异步Servlet示例</title>");
printWriter.println("进入Servlet的时间:" + new Date() + "<br/>");
printWriter.println("执行Servlet的线程ID:" + Thread.currentThread().getId() + " Name=" + Thread.currentThread().getName() + "<br/>"); // 创建AsyncContext,开始异步调用
final AsyncContext asyncContext = request.startAsync();
asyncContext.setTimeout(50 * 1000);// 设置异步调用的超时时长,限制HTTP响应的最大耗时 new Thread(() -> {
try {
Thread.sleep(1 * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
printWriter.println("执行业务处理的线程ID:" + Thread.currentThread().getId() + " Name=" + Thread.currentThread().getName() + "<br/>");
ServletResponse aResponse = asyncContext.getResponse();
/* ... print to the response ... */
printWriter.println("请求处理结束:" + new Date() + "<br/>");
asyncContext.complete();
}).start();
printWriter.println("退出Servlet的时间:" + new Date() + "<br/>");
}
}

异步模式带来的好处是什么?

从编程方面提供了异步能力,在编程环节就可以提前将耗时较高的Servlet启用异步模式。

异步模式具备拆分线程池解耦的能力,配置异步模式从专有work线程池取线程,而不是与原Servlet线程池共用线程,可以显著提高Servlet容器的并发量,减小对其它Serlvet请求的影响。

同时要看到:异步模式只能说让Tomcat有机会接收更多请求,并不能提升特定服务的吞吐量。

参考资料:

Java EE7官方文档目录:https://docs.oracle.com/javaee/7/tutorial/index.html

Servlet的异步处理:https://docs.oracle.com/javaee/7/tutorial/servlets012.htm

Servlet的非阻塞IO:https://docs.oracle.com/javaee/7/tutorial/servlets013.htm

Servlet特性研究之异步模式的更多相关文章

  1. 高性能的关键:Spring MVC的异步模式

    我承认有些标题党了,不过话说这样其实也没错,关于“异步”处理的文章已经不少,代码例子也能找到很多,但我还是打算发表这篇我写了好长一段时间,却一直没发表的文章,以一个更简单的视角,把异步模式讲清楚. 什 ...

  2. 基于Task的异步模式的定义

    返回该系列目录<基于Task的异步模式--全面介绍> 命名,参数和返回类型 在TAP(Task-based Asynchronous Pattern)中的异步操作的启动和完成是通过一个单独 ...

  3. 基于Task的异步模式--全面介绍

    今天是国庆长假第一天,也是今天十月的开始.每到这个时候都是看海的季节-一个看"人海"的季节.反正我是不想在这样一个尴尬期出去放松自己,于是不如在家写写博客,长点本领呢.今天就来给大 ...

  4. Spring MVC的异步模式

    高性能的关键:Spring MVC的异步模式   我承认有些标题党了,不过话说这样其实也没错,关于“异步”处理的文章已经不少,代码例子也能找到很多,但我还是打算发表这篇我写了好长一段时间,却一直没发表 ...

  5. Ansible系列(七):执行过程分析、异步模式和速度优化

    本文目录:1.1 ansible执行过程分析1.2 ansible并发和异步1.3 ansible的-t选项妙用1.4 优化ansible速度 1.4.1 设置ansible开启ssh长连接 1.4. ...

  6. Controller异步模式

    转载: https://blog.csdn.net/yingxiake/article/details/51193319 因为服务器请求处理线程的总数是有限的,如果类似的请求多了,所有的处理线程处于阻 ...

  7. Spring MVC的异步模式DefferedResult

    原文:http://www.importnew.com/21051.html 什么是异步模式 要知道什么是异步模式,就先要知道什么是同步模式,先看最典型的同步模式: (图1) 浏览器发起请求,Web服 ...

  8. 三、基于任务的异步模式(TAP),推荐使用

    一.引言 在上两个专题中我为大家介绍.NET 1.0中的APM和.NET 2.0中的EAP,在使用前面两种模式进行异步编程的时候,大家多多少少肯定会感觉到实现起来比较麻烦, 首先我个人觉得,当使用AP ...

  9. C11 标准特性研究

    前言 - 需要点开头 C11标准是C语言标准的第三版(2011年由ISO/IEC发布),前一个标准版本是C99标准. 相比C99,C11有哪些变化呢!!所有的测试全部基于能够和标准贴合的特性平台. 但 ...

随机推荐

  1. MySQL - 数据库设计步骤

    需求分析:分析用户的需求,包括数据.功能和性能需求. 概念结构设计:主要采用E-R模型进行设计,包括画E-R图. 逻辑结构设计:通过将E-R图转换成表,实现从E-R模型到关系模型的转换,进行关系规范化 ...

  2. AtCoder ABC 250 总结

    AtCoder ABC 250 总结 总体 连续若干次一样的结果:30min 切前 4 题,剩下卡在 T5 这几次卡在 T5 都是一次比一次接近, 什么 dp 前缀和打挂,精度被卡,能水过的题连水法都 ...

  3. Docker容器手动安装oracle19C

    Docker容器手动安装oracle19C docker容器体积小,与宿主机共用内核参数,因此修改宿主机的内核参数即是修改容器的内核参数 1.修改宿主机内核参数 [root@localhost ~]# ...

  4. 皓远的第一次博客作业(pta题目集——1-3)

    题目集总结: 前言: 一. 知识点运用: ①    Java入门的基础语法(循环,判断,字符串,数组等等),Java的基础类运用,类与对象关系调用,类间关系(聚合). ②    引(类与对象): 对象 ...

  5. TypeScript(5)类、继承、多态

    前言 对于传统的 JavaScript 程序我们会使用函数和基于原型的继承来创建可重用的组件,但对于熟悉使用面向对象方式的程序员使用这些语法就有些棘手,因为他们用的是基于类的继承并且对象是由类构建出来 ...

  6. 分享自己平时使用的socket多客户端通信的代码技术点和软件使用

    前言 说到linux下多进程通信,有好几种,之前也在喵哥的公众号回复过,这里再拿出来,重新写一遍:多进程通信有管道,而管道分为匿名和命名管道 ,后者比前者优势在于可以进行无亲缘进程通信:此外信号也是进 ...

  7. archlinux-小米pro15_2020款使用archlinux(MX350显卡驱动安装)

    1.官网下载archlinux ISO镜像 https://archlinux.org/download/   使用磁力链接下载 2.使用软碟通将镜像写入U盘,制作成U盘启动盘 3.进入BIOS 关掉 ...

  8. rhel安装程序

    Linux下软件分类     rpm软件包,包管理器 yum     deb软件包,包管理器 apt     源代码软件包            一般为".tar.gz".&quo ...

  9. NC14662 小咪买东西

    NC14662 小咪买东西 题目 题目描述 小咪是一个土豪手办狂魔,这次他去了一家店,发现了好多好多( \(n\) 个)手办,但他是一个很怪的人,每次只想买 \(k\) 个手办,而且他要让他花的每一分 ...

  10. (零)机器学习入门与经典算法之numpy的基本操作

    1.根据索引来获取元素* 创建一个索引列表ind,用来装载索引,当numpy数据是一维数据时:一个索引对应的是一个元素具体的例子如下: import numpy as np # 数据是一维数据时:索引 ...