基本上Servlet容器会为每一个Servlet注册名称实例化一个对象,来自用户端的请求会以一个线程来存取这个对象,如果有多个请求同时到达,就会有多个线程存取同一个Servlet实例,而这所引发的就是线程的安全问题。 如果您的Servlet实例之方法都是使用区域变量,这并不会有太大的问题,每一个线程对方法的调用会使用自己的区域变量,然而如果您的 Servlet方法中有共用一些field成员或是静态成员,在多个线程同时存取时,就会有线程安全问题,例如一个简单的计数器问题:
package onlyfun.caterpillar; 在上面这个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()中的一个区域变量,即使在多个用户端线程同时存取时,也不致于 发生线程共用资源的问题。 |