`
likenice
  • 浏览: 62203 次
  • 来自: 北京
社区版块
存档分类
最新评论

解决servlet非线程安全所导致jsp传参错误的一种方案

阅读更多
由于servlet是个线程池,而且是非线程安全,所以当压力过大的时候,容易导致一些奇怪的现象。
如下面的代码

 // 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两个线程的输出




如果是简单的将showUserInfo方法简单的synchronized并不能解决问题。(可以试一下)。

解决问题的办法是给方法中的变量传一个实例进取。

  <%!
//定义实例变量
String username;
String password;
java.io.PrintWriter output;
%>
<%
//从request中获取参数
username = request.getParameter("username");
password = request.getParameter("password");
output = response.getWriter();
showUserInfo(username,password,output);
//showUserInfo();
%>
<%!
public void showUserInfo(String user ,String pass,java.io.PrintWriter _output) {
//	public void showUserInfo() {
//为了突出并发问题,在这儿首先执行一个费时操作
int i =0;
double sum = 0.0;
while (i++ < 200000000) {
	sum += i;
}
_output.println(Thread.currentThread().getName() + "<br>");
_output.println("username:" + user + "<br>");
_output.println("password:" + pass + "<br>");
}
%>

这样就可以解决问题。

但我有点想不明白的是java传参数是传得引用,可是这里如果是引用的话那和上面的代码效果应该一样。所以推断,传得是实例。
问题时解决了,但我不想继续得过且过,不知道我那里想错了,请各位指点。

上面的例子摘自:http://publish.it168.com/2005/1209/20051209002201_hezuo.shtml?cChanNel=11&cpositioncode=296&hezuo=2

分享到:
评论
9 楼 likenice 2007-09-05  
flash 写道
其实这个问题很容易理解,主要是从两点来理解,
一是servlet的实例池;
另一个是java的参数传递方式。
servlet是被容器实例化后放入实例池中的,每一个servlet会有一个或多个实例,来处理针对该servlet的请求。因此,在高并发情况下,有可能会出现一个实例被多个请求调用的情况。
就拿楼主的第一个例子来讲,A用户在请求这个servlet后,调用了servlet实例池中的x实例,然后处理请求,当代码执行到“//为了突出并发问题,在这儿首先执行一个费时操作”这里时(此时实例x的实例变量username="userA"),刚好B用户也发出一个请求,刚好同样调用 了实例池中的x实例,然后将username修改为"userB"。然后也进入“//为了突出并发问题,在这儿首先执行一个费时操作”。正常情况下,应该是A的请球进程先执行完”这个费时操作“(其实哪个先后都无所谓了)。这时A的请求进程中输出username,肯定是“userB"。
在楼主的第二个例子为什么给方法加入参数就可以呢,这就是java方法的参数传递方式影响的。通常来讲,java 传递对象时是引用传递,传递基本类型时是值 传递。但用一种严格的说法,java传递参数的方式都是值传递,也就是传递的全是一个副本。只不过对对象而言,这个副本是该对像的一个引用副本。所以通常大家都讲,对象是引用传递。如果按照这种通常的说法,我们还应该把String类型的对象看成是值传递而不是引用传递,为什么?请大家看下面这个例子
public class StringTest {
static	String  aaa="aaa";
public static void main (String[] args){
	test(aaa);
}
public static void test(String a){
	aaa="bbb";
	System.out.println(a);
}
}

aaa的初始值是"aaa",被传入到test方法后,aaa的值被改写为"bbb"。最后输入a的值,仍然是"aaa"。
其实这个就是楼主提出问题的一个概括,只是放在一个进程中实现的罢了。在aaa变量 被传入test方法时,会创建一个aaa的引用副本,这个引用副本指向的是"aaa"这个对象。此时,有两个引用都指向"aaa"对象,他们分别是aaa和a。现在你再去给aaa赋值,只是把aaa的引用指向一个新的对象"bbb",而对"aaa"对象并任何影响。当然,jvm会不定时检测,如果"aaa"没有被任何引用指向,将会回收他的内存空间。但是在此时,在 test方法的运行栈里还有一个a引用来指向"aaa",所以"aaa"这个对象的不会被回收。现在的情况已经很明了,在内存就是有两个引用,aaa和a,他们分别指向"bbb"对象和"aaa"对象。这个情况是不是很像基本类型的值传递?为什么会这样,个人感觉应该是String的赋值方式给大家带来的错觉吧。String aaa="aaa",其实就是String aaa=new String("aaa");你重新给aaa赋值,就是重新创建了一个对象,然后指向他,感觉就像是对一个副本进行操作,而对原版没有影响一样。
呵呵,说的啰嗦了点,这方面可以看一下java传递相关研究的文章,比我说的清晰多了。

最后,再来分析楼主的第二个例子,就很容易理解了
由于在A请求中,调用showUserInfo(String user ,String pass,java.io.PrintWriter _output)方法时,内存中已经有user这个引用指向了对象"userA",那么当B请求来修改username时,只不过是把 username指向了一个新对象"userB"。然后在A请求中输出user的值 ,那肯定是它指向的"userA"了,与uaername已经完全没有关系了。
呵呵,精辟。
我想了半天不知道怎么表达。
(可能跟本人有点懒有关系吧。本来想画个图,结果画了几个不满意。)
在这里只能说楼上的辛苦了。
8 楼 flash 2007-09-05  
其实这个问题很容易理解,主要是从两点来理解,
一是servlet的实例池;
另一个是java的参数传递方式。
servlet是被容器实例化后放入实例池中的,每一个servlet会有一个或多个实例,来处理针对该servlet的请求。因此,在高并发情况下,有可能会出现一个实例被多个请求调用的情况。
就拿楼主的第一个例子来讲,A用户在请求这个servlet后,调用了servlet实例池中的x实例,然后处理请求,当代码执行到“//为了突出并发问题,在这儿首先执行一个费时操作”这里时(此时实例x的实例变量username="userA"),刚好B用户也发出一个请求,刚好同样调用 了实例池中的x实例,然后将username修改为"userB"。然后也进入“//为了突出并发问题,在这儿首先执行一个费时操作”。正常情况下,应该是A的请球进程先执行完”这个费时操作“(其实哪个先后都无所谓了)。这时A的请求进程中输出username,肯定是“userB"。
在楼主的第二个例子为什么给方法加入参数就可以呢,这就是java方法的参数传递方式影响的。通常来讲,java 传递对象时是引用传递,传递基本类型时是值 传递。但用一种严格的说法,java传递参数的方式都是值传递,也就是传递的全是一个副本。只不过对对象而言,这个副本是该对像的一个引用副本。所以通常大家都讲,对象是引用传递。如果按照这种通常的说法,我们还应该把String类型的对象看成是值传递而不是引用传递,为什么?请大家看下面这个例子
public class StringTest {
static	String  aaa="aaa";
public static void main (String[] args){
	test(aaa);
}
public static void test(String a){
	aaa="bbb";
	System.out.println(a);
}
}

aaa的初始值是"aaa",被传入到test方法后,aaa的值被改写为"bbb"。最后输入a的值,仍然是"aaa"。
其实这个就是楼主提出问题的一个概括,只是放在一个进程中实现的罢了。在aaa变量 被传入test方法时,会创建一个aaa的引用副本,这个引用副本指向的是"aaa"这个对象。此时,有两个引用都指向"aaa"对象,他们分别是aaa和a。现在你再去给aaa赋值,只是把aaa的引用指向一个新的对象"bbb",而对"aaa"对象并任何影响。当然,jvm会不定时检测,如果"aaa"没有被任何引用指向,将会回收他的内存空间。但是在此时,在 test方法的运行栈里还有一个a引用来指向"aaa",所以"aaa"这个对象的不会被回收。现在的情况已经很明了,在内存就是有两个引用,aaa和a,他们分别指向"bbb"对象和"aaa"对象。这个情况是不是很像基本类型的值传递?为什么会这样,个人感觉应该是String的赋值方式给大家带来的错觉吧。String aaa="aaa",其实就是String aaa=new String("aaa");你重新给aaa赋值,就是重新创建了一个对象,然后指向他,感觉就像是对一个副本进行操作,而对原版没有影响一样。
呵呵,说的啰嗦了点,这方面可以看一下java传递相关研究的文章,比我说的清晰多了。

最后,再来分析楼主的第二个例子,就很容易理解了
由于在A请求中,调用showUserInfo(String user ,String pass,java.io.PrintWriter _output)方法时,内存中已经有user这个引用指向了对象"userA",那么当B请求来修改username时,只不过是把 username指向了一个新对象"userB"。然后在A请求中输出user的值 ,那肯定是它指向的"userA"了,与uaername已经完全没有关系了。
7 楼 flash 2007-09-05  
xianyun 写道
 
不明白楼主为什么要定义静态变量
知道什么是静态变量吗?<%!和<%的区别吗?

另外,第二种方法你可以把延时放到前面这里再试一下

//为了突出并发问题,在这儿首先执行一个费时操作  
int i =0;  
double sum = 0.0;  
while (i++ < 200000000) {  
    sum += i;  
}
showUserInfo(username,password,output);  

<%!%>之内的并不是静态变量 只是一个实例变量。
6 楼 likenice 2007-09-04  
回楼上的。
我明白什么是全局变量。
如果您觉得可以不用全局变量,jsp中的方法内部怎么调用参数。(jsp的方法是全局的。而且必须是全局的)
当然这么写本身就是不推荐的。但我是看别人的东西引发的一些想法。重点不在我的前提条件怎么设置,而是结论。我的结论是错误的(我认为java参数是传得引用,原来认为:如果是引用则不能达到预期的效果。可实际中达到了。^.^!!!!很绕口,不知道说清楚了没。)
大家如果对代码由疑问,请测试一下。
5 楼 xianyun 2007-09-04  
 
不明白楼主为什么要定义静态变量
知道什么是静态变量吗?<%!和<%的区别吗?

另外,第二种方法你可以把延时放到前面这里再试一下

//为了突出并发问题,在这儿首先执行一个费时操作  
int i =0;  
double sum = 0.0;  
while (i++ < 200000000) {  
    sum += i;  
}
showUserInfo(username,password,output);  
4 楼 likenice 2007-09-04  
忘说了。如果说代码无法运行是由于美一行代码前面有隐藏的空格(此空格需要删除。这个是拷贝到网页上面的通病,所以要注意。)
3 楼 likenice 2007-09-04  
赫赫。想通了。
先在这里回答上面两位的问题。
1。代码绝对可以运行。
2。导致第一种问题(servlet线程池问题)的根源绝对是引用。

给大家提示下我们正常中一般给一个方法传个对象参数后,一般是在方法外面的到然后再处理。而对这个方法外面参数对象基本不会附值。引用(指针)方向就不会变。
今天忙。没时间说在清除。大家想想吧。很有意思。(和我们正常的class编码习惯不同)
2 楼 bluepopopo 2007-09-03  
确认第二种方法可行?定义为实例变量会有线程不安全,这与与是否引用无关
1 楼 JAVA_ED 2007-09-03  
你前面一种方法可以compile过吗
这里的uname,pwd.. 都是jspservice方法里的栈变量
不传param在别的method里应该取不到的

相关推荐

    servlet线程安全问题

    Servlet 线程安全问题是指在使用 Servlet 编程时,如果不注意多线程安全性问题,可能会导致难以发现的错误。Servlet/JSP 技术由于其多线程运行而具有很高的执行效率,但这也意味着需要非常细致地考虑多线程的安全性...

    jsp传参 servlet接收中文乱码问题的解决方法.docx

    jsp 传参 servlet 接收中文乱码问题的解决方法 jsp 传参 servlet 接收中文乱码问题是一个经常遇到的问题,特别是在使用 Hibernate+Servlet 框架时。当我们在 jsp 页面传参到 servlet 时,中文字符经常会出现乱码...

    JSP页面传参出现中文乱码的解决方案

    - **编码与解码不匹配**:当JSP页面、Servlet或服务器配置的字符编码不一致时,中文字符在传输过程中可能被错误地编码或解码,从而导致乱码。 - **默认编码问题**:Java Web容器(如Tomcat)默认可能使用ISO-8859-...

    servlet与Struts action线程安全问题分析

    解决Servlet的线程安全问题通常有以下几种策略: 1. **避免使用实例变量**:尽可能使用局部变量,局部变量只存在于方法的执行上下文中,不会被多个线程共享,因此不存在线程安全问题。 2. **使用同步控制**:通过`...

    Servlet线程安全的解决方法

    在探讨Servlet线程安全的解决方案之前,我们先来了解一下为何会出现线程安全问题。当多个线程同时访问同一个Servlet时,如果这些线程同时修改了Servlet中的共享资源(例如实例变量),那么就可能会导致数据不一致性...

    Servlet是线程不安全的1

    Servlet是一种线程不安全的组件,它的线程不安全性体现在多线程环境下共享一个实例变量,导致线程安全问题。下面我们将从Servlet的工作原理说起,详细解释Servlet接收和响应客户请求的过程,并探讨Servlet线程不安全...

    servlet传参

    Servlet 是一个运行在服务器端的单实例多线程的服务器端 Java 应用程序。理解 Servlet 的生命周期和接受参数信息是掌握 Servlet 技术的关键。 Servlet 的生命周期 Servlet 的生命周期可以分为四个阶段: 1. 初始...

    jsp,servlet分页SQL传参Servlet翻页

    在Java Web开发中,`JSP (JavaServer Pages)`、`Servlet`以及`SQL`的交互是实现动态网页的关键技术。本话题主要关注如何通过`Servlet`处理分页查询,并将参数传递给`SQL`来实现数据库数据的翻页显示。下面我们将详细...

    深入研究Servlet线程安全性问题.pdf

    针对Servlet线程安全问题,通常有以下几种解决方案: 1. **使用局部变量**:尽量避免使用实例变量,转而使用局部变量,这样每个线程都会有自己的副本,不会出现数据冲突。 2. **同步机制**:利用Java的...

    Servlet线程安全问题.docx

    当第一个请求到达时,Tomcat会创建Servlet实例,后续的请求将复用同一个实例,这意味着多个线程可能同时访问Servlet的同一实例,从而引发线程安全问题。 Servlet线程池机制 Tomcat容器为了高效地处理请求,采用了一...

    servlet多线程

    然而,现代Servlet容器更倾向于使用多实例策略来缓解单一线程模型带来的效率问题,即创建一个Servlet实例池,每个请求分配一个实例进行处理,处理完毕后实例返回池中等待下一次分配,以此平衡性能与线程安全。

    深入研究Servlet线程安全性问题

    为解决Servlet的线程安全问题,有以下几种策略: 1. **使用synchronized关键字**:可以通过在Servlet方法上添加`synchronized`关键字,确保同一时间只有一个线程能执行该方法,从而避免数据冲突。但这种方式会影响...

    servlet与Struts action线程安全问题分析(pdf)

    本文将深入探讨Servlet及其衍生框架Struts中的线程安全问题,并提出相应的解决方案。 #### 二、Servlet的多线程机制 Servlet的生命周期是由Web容器管理的。当客户端首次请求某个Servlet时,容器会根据`web.xml`...

    servlet-api.jar和jsp-api.jar文件

    Servlet-api.jar和jsp-api.jar是Java Web开发中两个非常重要的库文件,它们包含了Servlet和JSP(JavaServer Pages)的相关API,使得开发者可以构建动态Web应用程序。这两个文件通常由Java EE(Enterprise Edition)...

    jsp网页产生json传值到java的servlet,再回传到jsp页面

    在本场景中,我们探讨的是如何使用JSP通过Ajax(Asynchronous JavaScript and XML)发送JSON(JavaScript Object Notation)数据到Java的Servlet,然后Servlet处理这些数据并返回JSON响应给JSP页面。这是一个典型的...

    Head First Servlet & JSP

    《Head First Servlet & JSP》是一本非常受欢迎的IT教程,专为准备SCWCD(Sun Certified Web Component Developer)认证的读者设计。本书以其独特的学习风格,深入浅出地介绍了Servlet和JSP(JavaServer Pages)这两...

Global site tag (gtag.js) - Google Analytics