JSP线程安全
一、概念
JSP与其他脚本语言不同,JSP默认是以多线程方式执行的,在执行时可能会存在多个用户同时读取一个变量的问题。
首先了解一下类变量、实例变量、局部变量之间的关系。
1. 类变量
request,response,session,config,application,以及JSP页面内置的page, pageContext。
其中除了application外,其它都是线程安全的。
2. 实例变量
实例变量是实例所有的,在堆中分配。在Servlet/JSP容器中,一般仅实例化一个Servlet/JSP实例,
启动多个该实例的线程来处理请求。而实例变量是该实例所有的线程所共享,所以,实例变量不是线程安全的。
3. 局部变量
局部变量在堆栈中分配,因为每一个线程有自己的执行堆栈,所以,局部变量是线程安全的。
实例变量与局部变量在JSP中的定义方法:
<%! %>内的变量和方法是一个类内的变量和方法也就是成员变量和成员方法。
<%%>内的变量是一个方法的变量也就是局部变量。
二、问题
先看以下代码
<%! //定义实例变量 String username; String password; java.io.PrintWriter output; %> <% //从request中获取参数 username = request.getParameter("username"); password = request.getParameter("password"); output = response.getWriter(); showUserInfo(); %> <%! public void showUserInfo() { //为了突出并发问题,在这儿首先执行一个费时操作 int i =0,j=0,k=0; double sum = 0.0; while (i++ < 999999999) { while(j++<999999999){ while(k++ < 999999999){ sum += i; } } } output.println(Thread.currentThread().getName() + "<br>"); //获取当前进程的名称 output.println("username:" + username + "<br>"); output.println("password:" + password + "<br>"); } %>
在这个页面中,首先定义了两个实例变量,username和password。然后在从request中获取这两个参数,并调用showUserInfo()方法将请求用户的信息回显在该客户的浏览器上。在一个用户访问时,不存在问题。但在多个用户并发访问时,就会出现其它用户的信息显示在另外一些用户的浏览器上的问题。这是一个严重的问题。为了突出并发问题,便于测试、观察,我们在回显用户信息时执行了一个模拟的费时操作,比如,下面的两个用户同时访问(可以启动两个IE浏览器同时访问):
http://localhost:8080/3/1.jsp?username=a&password=123 http://localhost:8080/3/1.jsp?username=b&password=456
如果a点击链接后,b再点击链接,那么,a将返回一个空白屏幕,b则得到a以及b两个线程的输出:
这是因为上面程序中的output, username和password都是实例变量,是所有线程共享的。在a访问该页面后,将output设置为a的输出,username,password分别置为a的信息,而在a执行printUserInfo()输出username和password信息前,b又访问了该页面,把username和password置为了b的信息,并把输出output指向到了b。随后a的线程打印时,就打印到了b的屏幕了,并且,a的用户名和密码也被b的取代。
实际中必须两个操作的间隔时间很短,当在大量访问时可能会出现这种问题。
三、解决方式
1.去除实例变量,通过参数传递
代码如下
<% //使用局部变量 String username; String password; java.io.PrintWriter output; //从request中获取参数 username = request.getParameter("username"); password = request.getParameter("password"); output = response.getWriter(); showUserInfo(output, username, password); %> <%! public void showUserInfo(java.io.PrintWriter _output, String _username, String _password) { //为了突出并发问题,在这儿首先执行一个费时操作 int i =0,j=0,k=0; double sum = 0.0; while (i++ < 999999999) { while(j++<999999999){ while(k++ < 999999999){ sum += i; } } } _output.println(Thread.currentThread().getName() + "<br>"); _output.println("username:" + _username + "<br>"); _output.println("password:" + _password + "<br>"); } %>
运行结果如下:
通过定义局部变量,并参数进行传递。这样,由于局部变量是在线程的堆栈中进行分配的,所以是线程安全的。不会出现多线程同步的问题
2.以单线程方式运行
JSP界面设置如下
<%@page isThreadSafe="false"%>
默认为true,是多线程模式
如果将JSP或Servlet设置成单线程工作模式,会导致每个请求创建一个Servlet实例,这种实践将导致严重的性能问题(服务器的内存压力很大,还会导致频繁的垃圾回收),所以通常情况下并不会这么做。
参考: