大可制作:QQ群:31564239(asp|jsp|php|mysql)

JSP/Servlet: Servlet 线程安全

基本上Servlet容器会为每一个Servlet注册名称实例化一个对象,来自用户端的请求会以一个线程来存取这个对象,如果有多个请求同时到达,就会有多个线程存取同一个Servlet实例,而这所引发的就是线程的安全问题。

如果您的Servlet实例之方法都是使用区域变量,这并不会有太大的问题,每一个线程对方法的调用会使用自己的区域变量,然而如果您的 Servlet方法中有共用一些field成员或是静态成员,在多个线程同时存取时,就会有线程安全问题,例如一个简单的计数器问题:

  • ServletDemo.java
package onlyfun.caterpillar; 

import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;

public class ServletDemo extends HttpServlet {
private int count = 0;

public void doGet(HttpServletRequest req,
HttpServletResponse res)
throws ServletException, IOException {
PrintWriter out = res.getWriter();
count++;
out.println("This servlet is accessd " + count +
"times");
}
}

在上面这个Servlet中,由于count是field成员,在Servlet载入后,只有一个Servlet实例,每个请求以一个线程来存取这个实 例,有可能发生在前一个线程正在执行out.println(),而后一个线程正好执行count++,这会导致 count的计数不连续的情况发生,这个情况在这个例子中并不致于引发多大的问题,但它所代表的是多线程存取同一个Servlet的线程安全问题。

要求线程安全最基本的,就是对共用资源作同步化,例如对doGet()使用synchronized关键字:
public synchronized void doGet(HttpServletRequest req,
                                HttpServletResponse res)
 

或者是:
....
    PrintWriter out = res.getWriter();
    synchronized(this) {
        count++;
        out.println("This servlet is accessd " + count +
                    "times");
    }
....
 

同步化所带来的是延迟,当同步化的区块被锁定时,其他的线程必须等待锁定的解除,这所代表的就是对使用者请求的延迟,在服务器上这点延迟在用户端多时就 会导致使用者必须花费长时间来等待回应,可以将同步化区块尽量缩小来减少延迟,或者是完全使用区域变量,使得线程之间不共用某些资源,然后不共用资源所 带来的,就是某些信息将无法实现持续性,为了实现持续性可能必须额外花费一些手续。

回顾一下JSP中在<%! 与 %>之间声明的变量,在生成Servlet之后,就是生成一个field成员,所以在<%! 与 %>之间声明变量,也必须小心线程安全问题,通常并不建议这么声明变量,而是在<% 与 %>之间声明变量,因为在转为Servlet之间,它是_jspService()中的一个区域变量,即使在多个用户端线程同时存取时,也不致于 发生线程共用资源的问题。