servlet
Servlet/JSP技术和ASP、PHP等相比,由于其多线程运行而具有很高的执行效率。由于Servlet/JSP默认是以多线程模式执行的,所以,在编写代码时需要非常细致地考虑多线程的同步问题。然而,很多人编写Servlet/JSP程序时并没有注意到多线程同步的问题,这往往造成编写的程序在少量用户访问时没有任何问题,而在并发用户上升到一定值时,就会经常出现一些莫明其妙的问题,对于这类随机性的问题调试难度也很大。
一、在Servlet/JSP中的几种变量类型
在编写Servlet/JSP程序时,对实例变量一定要小心使用。因为实例变量是非线程安全的。在Servlet/JSP中,变量可以归为下面的几类:
1. 类变量
request,response,session,config,application,以及JSP页面内置的page, pageContext。其中除了application外,其它都是线程安全的。
2. 实例变量
实例变量是实例所有的,在堆中分配。在Servlet/JSP容器中,一般仅实例化一个Servlet/JSP实例,启动多个该实例的线程来处理请求。而实例变量是该实例所有的线程所共享,所以,实例变量不是线程安全的。
3. 局部变量
局部变量在堆栈中分配,因为每一个线程有自己的执行堆栈,所以,局部变量是线程安全的。
二、在Servlet/JSP中的多线程同步问题
在JSP中,使用实例变量要特别谨慎。首先请看下面的代码:
<ccid_code>// instanceconcurrenttest.jsp
");
output.println("username:" + username + "<br>");
output.println("password:" + password + "<br>");
}
%></ccid_code> |
在这个页面中,首先定义了两个实例变量,username和password。然后在从request中获取这两个参数,并调用showUserInfo()方法将请求用户的信息回显在该客户的浏览器上。在一个用户访问是,不存在问题。但在多个用户并发访问时,就会出现其它用户的信息显示在另外一些用户的浏览器上的问题。这是一个严重的问题。为了突出并发问题,便于测试、观察,我们在回显用户信息时执行了一个模拟的费时操作,比如,下面的两个用户同时访问(可以启动两个IE浏览器,或者在两台机器上同时访问):
a: http://localhost:8080/instanceconcurrenttest.jsp?username=a&password=123
b: http://localhost:8080/instanceconcurrenttest.jsp?username=b&password=456
如果a点击链接后,b再点击链接,那么,a将返回一个空白屏幕,b则得到a以及b两个线程的输出。请看下面的屏幕截图:
图1:a的屏幕
图2:b的屏幕
从运行结果的截图上可以看到,Web服务器启动了两个线程分别来处理来自a和b的请求,但是在a却得到一个空白的屏幕。这是因为上面程序中的output, username和password都是实例变量,是所有线程共享的。在a访问该页面后,将output设置为a的输出,username,password分别置为a的信息,而在a执行printUserInfo()输出username和password信息前,b又访问了该页面,把username和password置为了b的信息,并把输出output指向到了b。随后a的线程打印时,就打印到了b的屏幕了,并且,a的用户名和密码也被b的取代。请参加下图所示:
图3:a、b两个线程的时间线
而实际程序中,由于设置实例变量,使用实例变量这两个时间点非常接近,所以,像本例的同步问题并没有这么突出,可能会偶尔出现,但这却更加具有危险性,也更加难于调试。
同样,对于Servlet也存在实例变量的多线程问题,请看上面页面的Servlet版:
<ccid_code>// InstanceConcurrentTest.java
import javax.servlet.*;
import javax.servlet.http.*;
import java.io.PrintWriter;
public class InstanceConcurrentTest extends HttpServlet
{
String username;
String password;
PrintWriter out;
public void doGet(HttpServletRequest request,
HttpServletResponse response)
throws ServletException,java.io.IOException
{
//从request中获取参数
username = request.getParameter("username");
password = request.getParameter("password");
System.out.println(Thread.currentThread().getName() +
" | set username:" + username);
out = response.getWriter();
showUserInfo();
}
public void showUserInfo() {
//为了突出并发问题,在这儿首先执行一个费时操作
int i =0;
double sum = 0.0;
while (i++ </ccid_code> |
三、解决方案
1. 以单线程运行Servlet/JSP
在JSP中,通过设置:,在Servlet中,通过实现javax.servlet.SingleThreadModel,此时Web容器将保证JSP或Servlet实例以单线程方式运行。
重要提示:在测试中发现,Tomcat 4.1.17不能正确支持isThreadSafe属性,所以,指定isTheadSafe为false后,在Tomcat 4.1.17中仍然出现多线程问题,这是Tomcat 4.1.17的Bug。在Tomcat 3.3.1和Resin 2.1.5中测试通过。
2. 去除实例变量,通过参数传递
从上面的分析可见,应该在Servlet/JSP中尽量避免使用实例变量。比如,下面的修正代码,去除了实例变量,通过定义局部变量,并参数进行传递。这样,由于局部变量是在线程的堆栈中进行分配的,所以是线程安全的。不会出现多线程同步的问题。代码如下:
<ccid_code>
");
_output.println("username:" + _username + "<br>");
_output.println("password:" + _password + "<br>");
}
%></ccid_code> |
注:有的资料上指出在printUserInfo()方法或者实例变量的相关操作语句上使用synchronized关键字进行同步,但这样并不能解决多线程的问题。因为,这样虽然可以使对实例变量的操作代码进行同步,但并不能阻止一个线程使用另外一个线程修改后的“脏的”实例变量。所以,除了降低运行效率外,不会起到预期效果。
Statement:The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn