Servlet 简介
Servlet 的技术定位、与 CGI 的差别、容器提供的服务。
Servlet 技术
A servlet is a platform-independent server-side component, compiled to bytecode and runnable in any container implementing the spec. It offers a request–response model: the container turns one HTTP exchange into one method call.
- 高效 —— 每个请求一个线程,不是每个请求一个进程
- 便利 —— 容器解析报文、管理连接、处理编码
- 可移植 —— 换容器不改代码
- 健壮 —— 容器隔离故障、回收线程、复用实例
Servlet 与 CGI 的区别
| Servlet | ||
|---|---|---|
| Concurrency | 每个请求 fork 一个进程 | 每个请求一个线程,实例复用 |
| Memory | 进程级,几十 MB 起 | 线程级,几百 KB |
| Startup | 每次都要加载解释器 | 只初始化一次 init() |
| Portability | 依赖平台脚本 | 纯 Java 字节码 |
容器替你做的事
You almost never touch network programming in a servlet, because the container has already done it:
- 监听端口、接受连接、解析 HTTP 报文
- 把请求映射到对应的 Servlet 类
- 调用
init()一次,之后并发调用service() - 管理 Session,生成并校验
JSESSIONID - 应用停止时调用
destroy(),回收资源
实例变量为什么不安全
@WebServlet("/count")
public class BadCounter extends HttpServlet {
private int count = 0; // 所有请求共享,并发下会丢更新
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws IOException {
count++; // 读-改-写不是原子操作
resp.getWriter().print(count);
}
}The container creates only one instance, and dozens of threads run count++ at once; two threads may read the same stale value and one update is lost. Keep state in an AtomicInteger, the database or the session — not in a bare instance field.
AtomicXxx or concurrent collections over synchronized on the whole method.Servlet 技术
Servlet 是平台无关的服务端组件,编译成字节码,跑在任何支持 Servlet 规范的容器里。它提供的是请求—响应编程模型:容器把一次 HTTP 交互变成一次方法调用。
- 高效 —— 每个请求一个线程,不是每个请求一个进程
- 便利 —— 容器解析报文、管理连接、处理编码
- 可移植 —— 换容器不改代码
- 健壮 —— 容器隔离故障、回收线程、复用实例
Servlet 与 CGI 的区别
| CGI | Servlet | |
|---|---|---|
| 并发模型 | 每个请求 fork 一个进程 | 每个请求一个线程,实例复用 |
| 内存开销 | 进程级,几十 MB 起 | 线程级,几百 KB |
| 启动成本 | 每次都要加载解释器 | 只初始化一次 init() |
| 可移植性 | 依赖平台脚本 | 纯 Java 字节码 |
容器替你做的事
写 Servlet 时你几乎不碰网络编程,因为容器已经把这些做完了:
- 监听端口、接受连接、解析 HTTP 报文
- 把请求映射到对应的 Servlet 类
- 调用
init()一次,之后并发调用service() - 管理 Session,生成并校验
JSESSIONID - 应用停止时调用
destroy(),回收资源
实例变量为什么不安全
@WebServlet("/count")
public class BadCounter extends HttpServlet {
private int count = 0; // 所有请求共享,并发下会丢更新
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws IOException {
count++; // 读-改-写不是原子操作
resp.getWriter().print(count);
}
}容器只创建一个实例,几十个线程同时执行 count++,两个线程可能读到同一个旧值,结果少加。正确做法是把状态放到 AtomicInteger、数据库或 Session 里,而不是裸的实例字段。
Servlet 是单实例多线程的。只要不在实例字段里存请求相关的数据,就不用加锁;一旦要共享可变状态,优先用 AtomicXxx 或并发容器,而不是 synchronized 整个方法。