详解Servlet 3.0/3.1 中的异步处理
发布时间 - 2026-01-11 00:32:22 点击率:次在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)实现下拉菜单异步取值
# 并在
# 我们可以
# 可以通过
# 在上
# 引入了
# 池中
# 都是
# 客户端
# 在这个
# 构成了
# 下载地址
# 下了
# 是从
# 将其
# 这是一个
# 用了
# 也将
# 便可
# 将被
# 因为他们
相关栏目:
【
网站优化151355 】
【
网络推广146373 】
【
网络技术251813 】
【
AI营销90571 】
相关推荐:
香港服务器网站搭建教程-电商部署、配置优化与安全稳定指南
Laravel观察者模式如何使用_Laravel Model Observer配置
网站制作怎么样才能赚钱,用自己的电脑做服务器架设网站有什么利弊,能赚钱吗?
详解Android中Activity的四大启动模式实验简述
Laravel如何处理CORS跨域请求?(配置示例)
如何基于云服务器快速搭建网站及云盘系统?
如何在搬瓦工VPS快速搭建网站?
网站制作软件免费下载安装,有哪些免费下载的软件网站?
Laravel怎么导出Excel文件_Laravel Excel插件使用教程
佛山网站制作系统,佛山企业变更地址网上办理步骤?
企业在线网站设计制作流程,想建设一个属于自己的企业网站,该如何去做?
怎么用AI帮你为初创公司进行市场定位分析?
Laravel怎么使用Intervention Image库处理图片上传和缩放
如何在云主机上快速搭建网站?
java中使用zxing批量生成二维码立牌
MySQL查询结果复制到新表的方法(更新、插入)
专业型网站制作公司有哪些,我设计专业的,谁给推荐几个设计师兼职类的网站?
Laravel项目如何进行性能优化_Laravel应用性能分析与优化技巧大全
Laravel如何使用Gate和Policy进行权限控制_Laravel权限判定与策略规则配置
如何快速生成凡客建站的专业级图册?
laravel怎么为API路由添加签名中间件保护_laravel API路由签名中间件保护方法
Laravel如何与Vue.js集成_Laravel + Vue前后端分离项目搭建指南
Laravel怎么处理异常_Laravel自定义异常处理与错误页面教程
详解Nginx + Tomcat 反向代理 如何在高效的在一台服务器部署多个站点
Laravel的路由模型绑定怎么用_Laravel Route Model Binding简化控制器逻辑
百度输入法ai面板怎么关 百度输入法ai面板隐藏技巧
如何获取上海专业网站定制建站电话?
如何用y主机助手快速搭建网站?
JS中页面与页面之间超链接跳转中文乱码问题的解决办法
laravel怎么使用数据库工厂(Factory)生成带有关联模型的数据_laravel Factory生成关联数据方法
如何在 Python 中将列表项按字母顺序编号(a.、b.、c. …)
Laravel Eloquent:优雅地将关联模型字段扁平化到主模型中
长沙做网站要多少钱,长沙国安网络怎么样?
Laravel如何实现本地化和多语言支持?(i18n教程)
浅述节点的创建及常见功能的实现
如何在万网利用已有域名快速建站?
Laravel如何为API生成Swagger或OpenAPI文档
Laravel的.env文件有什么用_Laravel环境变量配置与管理详解
C语言设计一个闪闪的圣诞树
Python高阶函数应用_函数作为参数说明【指导】
如何彻底卸载建站之星软件?
简历在线制作网站免费版,如何创建个人简历?
高性价比服务器租赁——企业级配置与24小时运维服务
如何用AWS免费套餐快速搭建高效网站?
Laravel如何实现API版本控制_Laravel版本化API设计方案
HTML透明颜色代码怎么让下拉菜单透明_下拉菜单透明背景指南【技巧】
如何在IIS7中新建站点?详细步骤解析
微信小程序 wx.uploadFile无法上传解决办法
Python结构化数据采集_字段抽取解析【教程】
如何快速搭建高效WAP手机网站?

