Servlet 线程安全问题
Tomcat 容器会为每个请求分配一个线程来处理,每个线程都调用同一个 Servlet 实例对象,只是每个线程的 HttpServletRequest 和 HttpServletResponse 对象不同。既然是同一个 Servlet 对象,如果定义了全局变量,就可能出现线程安全问题。
存在线程安全问题的代码
public class HomeServlet extends HttpServlet {
private int i; // 全局变量
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
i++;
response.getWriter().write(i + " ");
}
}Code language: PHP (php)
i 是所有请求共享的,因此不管换浏览器还是换电脑,访问的都是同一个 i,其值会一直递增。

不存在线程安全问题的代码
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
int i = 1; // 局部变量
i++;
response.getWriter().write(String.valueOf(i));
}Code language: JavaScript (javascript)
i 是方法内的局部变量,每次执行都会重新开辟内存,因此每次输出都是 2。

使用 synchronized 解决线程安全
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
synchronized (this) { // this 指 Servlet 实例对象
i++;
try {
Thread.sleep(1000 * 4);
} catch (InterruptedException e) {
e.printStackTrace();
}
response.getWriter().write(i + "");
}
}Code language: JavaScript (javascript)
synchronized 保证同一时间只有一个线程能访问,其他线程必须排队。如果有大量并发请求,会导致阻塞等待,性能较差。
SingleThreadModel 接口(已过时)
Sun 公司曾提供 SingleThreadModel 接口来解决线程安全问题,让 Servlet 引擎以单线程模式调用 service 方法。该接口是一个标记接口(无任何方法和常量),类似于 Serializable 和 Cloneable。
public class HomeServlet extends HttpServlet implements SingleThreadModel {
// ...
}Code language: PHP (php)
实现该接口后,Servlet 引擎仍然支持多线程并发访问,但会创建多个 Servlet 实例对象,每个线程调用一个独立的实例。这种方式并不能真正解决一个 Servlet 实例被多个线程同时调用的问题,因此在 Servlet API 2.4 中已被标记为 Deprecated(过时)。
总结
| 方案 | 说明 | 问题 |
|---|---|---|
| 局部变量 | 每次请求独立分配 | 无线程安全问题,推荐 |
synchronized | 加锁串行执行 | 性能差,高并发阻塞 |
SingleThreadModel | 多实例隔离 | 已过时,不能真正解决单实例并发问题 |
Previous: 缺省Servlet
Next: ServletConfig配置初始化参数