Servlet 的线程安全问题

Servlet 线程不安全示例

public class Hello extends HttpServlet {
    PrintWriter output;

    @Override
    protected void service(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        String username;
        response.setContentType("text/html;charset=UTF-8");
        username = request.getParameter("username");
        output = response.getWriter();
        try {
            //为了突出并发问题,在这设置一个延时
            Thread.sleep(5000);
            output.println("用户名:" + username + "<BR>");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

同时在浏览器开两个页面分别访问 http://localhost:8080/hello?username=aahttp://localhost:8080/hello?username=bb 刷新几次就很容易遇到一个页面没有输出,一个页面输出了两句的情况。

Servlet 线程不安全的原因

Servlet 的多线程机制

我们知道,Servlet 是单实例的,所有的请求到某一个 Servlet 最终执行的都是同一个示例。它在 web 容器中的执行过程是这样的。
web 容器,我们这里以 Tomcat 为例。Tomcat 中维护着一个线程池,这个线程池叫工作组线程池(Work Thread Tool),Tomcat 使用一个调度线程(Dispatcher)来管理这些工作组线程。每收到一个请求,调度线程就从线程池中选出一个工作组线程,将请求传递给这个线程,让它来执行和请求对应的 Servlet 的 Service() 方法。
所以如果一个线程正在执行某一个 Servlet,这时来了另一个请求也是请求这个 Servlet 的,那么容器会启用另一个线程来处理这个请求。这个 Servlet 的 Service 方法就要在多线程中并发执行。

Servlet 线程安全的解决办法

变量的线程安全

受影响的变量主要是 Servlet 类的成员变量,其方法下的局部变量没有问题,所以成员变量改为局部变量即可。

属性的线程安全

ServletContext: 是线程不安全的,多个线程下同时读写需要进行同步或者深度克隆。
HttpSession: 也是线程不安全的,和上面的操作一样。

集合类的同步

在 Servlet 中使用集合类的时候,需要选择线程安全的集合类。

对外部对象的加锁

Servlet 中访问的外部对象(例如文件)必须加锁。不过加锁会影响执行效率。

SingleThreadModel 接口

如果一个 Servlet 实现了该接口,那么 Tomcat 将保证在一个时刻只有一个工作组线程可以在 Servlet 的 Service 方法中执行。
不过这样会导致其他请求需要排队,会影响效率。