Servlet及JSP中的多线程同步问题

news/2025/1/12 9:51:39 标签: 多线程, servlet, web
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中,使用实例变量要特别谨慎。首先请看下面的代码:

// instanceconcurrenttest.jsp
<%@ page contentType="text/html;charset=GBK" %>
<%!
//定义实例变量
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;
double sum = 0.0;
while (i++ < 200000000) {
sum += i;
}

output.println(Thread.currentThread().getName() + "<br>");
output.println("username:" + username + "<br>");
output.println("password:" + password + "<br>");
}
%>


  在这个页面中,首先定义了两个实例变量,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版:

// 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++ < 200000000) {
sum += i;
}
out.println("thread:" + Thread.currentThread().getName());
out.println("username:"+ username);
out.println("password:" + password);
}
}


  三、解决方案

  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中尽量避免使用实例变量。比如,下面的修正代码,去除了实例变量,通过定义局部变量,并参数进行传递。这样,由于局部变量是在线程的堆栈中进行分配的,所以是线程安全的。不会出现多线程同步的问题。代码如下:

<%@ page contentType="text/html;charset=GBK" %>
<%
//使用局部变量
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;
double sum = 0.0;
while (i++ < 200000000) {
sum += i;
}
_output.println(Thread.currentThread().getName() + "<br>");
_output.println("username:" + _username + "<br>");
_output.println("password:" + _password + "<br>");
}
%>


注:有的资料上指出在printUserInfo()方法或者实例变量的相关操作语句上使用synchronized关键字进行同步,但这样并不能解决多线程的问题。因为,这样虽然可以使对实例变量的操作代码进行同步,但并不能阻止一个线程使用另外一个线程修改后的“脏的”实例变量。所以,除了降低运行效率外,不会起到预期效果。
摘自:http://www.neu.edu.cn/network/show.php?id=35

http://www.niftyadmin.cn/n/1052633.html

相关文章

CSS 1. 选择器

1、css的介绍 CSS是指层叠样式表(Cascading Style Sheets)&#xff0c;样式定义如何显示HTML元素&#xff0c;样式通常又会存在于样式表中。也就是说把HTML元素的样式都统一收集起来写在一个地方或一个CSS文件里。 css的优势 1.内容与表现分离 2.网页的表现统一&#xff0c;容…

java实现mysql数据库从一张表插入数据到另一张表

创建两张表&#xff1a; create table employee(id varchar(18),name varchar(18),email varchar(100),gender varchar(10) );create table copyEmployee(id varchar(18),name varchar(18),email varchar(100),gender varchar(10) ); 插入数据: insert into employee values(&q…

IE8 开发人员工具不弹出窗口解决方法

按F12时任务栏里出现开发人员工具的任务&#xff0c;但是开发人员工具窗体不弹出&#xff0c;也不出现在IE8里&#xff0c;重装IE88后还是存在此问题。 解决方法&#xff1a; 在IE8开发工具任务栏图标上的缩略图上点右键-最大化&#xff0c;然后就出来了。

学习Jakarta Struts 1.1 (一)

本文一部分是出自Sue Spielman的书《The Struts Framework: Practical Guide for Java Programmers (Morgan-Kaufmann) / Struts框架Java程序员实用指南》。这本书是市场上全面的具体的介绍Struts 1.1的首批图书。你可以通过Sue的邮箱&#xff08;sspielmanswitchbacksoftware.…

帆软报表(finereport)根据提供的数据求出该日期所在的季度

根据当前日期求字段中日期的季度 Oracle数据库 select T1.INDEXCODE ,T1.CREATETIME ,CASE when T1.CREATETIME (case when to_char(SYSDATE,MM)/3-1 < 0 then TRIM(to_char(SYSDATE,YYYY)-1)||-||TRIM(4(to_char(SYSDATE,MM)/3-1)) else TRIM(to_char(SYSDATE,YYYY))|…

写在那个想家的日子

今天是2012年10月21日&#xff0c;北漂一年零二个月了&#xff0c;有一段没写博客了&#xff0c;一是这段时间比较忙&#xff0c;刚入职&#xff0c;一下子有很多的东西要学&#xff0c;二是觉得刚入行&#xff0c;才疏学浅&#xff0c;想把知识一块一块的吃透了再写写心得。入…

重温位运算、原码、反码、补码、以及和区别

一个例子说明原码&#xff0c;反码&#xff0c;补码&#xff1a; 下面进行5和-5的原码&#xff0c;反码&#xff0c;补码表示&#xff1a; 5的原码&#xff1a;0000 0101 5的反码&#xff1a;0000 0101 5的补码&#xff1a;0000 0101 -5的原码&#xff1a;1000 0101 -5的反码&a…

thinkPHP5.0获取器获取原始数据

如果你定义了获取器的情况下&#xff0c;希望获取数据表中的原始数据&#xff0c;可以使用&#xff1a;$cate Cate::get(1);// 通过获取器获取字段echo $cate->type;// 获取原始字段数据echo $cate->getData(type);// 获取全部原始数据dump($cate->getData());转载于…