Servlet 线程安全问题

Servlet 线程安全问题

Tomcat 容器会为每个请求分配一个线程来处理,每个线程都调用同一个 Servlet 实例对象,只是每个线程的 HttpServletRequestHttpServletResponse 对象不同。既然是同一个 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 方法。该接口是一个标记接口(无任何方法和常量),类似于 SerializableCloneable

public class HomeServlet extends HttpServlet implements SingleThreadModel {
    // ...
}Code language: PHP (php)

实现该接口后,Servlet 引擎仍然支持多线程并发访问,但会创建多个 Servlet 实例对象,每个线程调用一个独立的实例。这种方式并不能真正解决一个 Servlet 实例被多个线程同时调用的问题,因此在 Servlet API 2.4 中已被标记为 Deprecated(过时)

总结

方案说明问题
局部变量每次请求独立分配无线程安全问题,推荐
synchronized加锁串行执行性能差,高并发阻塞
SingleThreadModel多实例隔离已过时,不能真正解决单实例并发问题

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注