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

解决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里应该取不到的

相关推荐

    U盘量产工具SM3280&3281&3282-AvidiaV0209整合版

    U盘量产工具FLASH量产工具SM3280&3281&3282-AvidiaV0209整合版

    java课程期末考试.zip

    java课程期末考试

    分布式消息中间件,参考kafka,未完成.zip

    分布式消息中间件,参考kafka,未完成

    修木工施工规范及流程.docx

    修木工施工规范及流程.docx

    汽车电子中MICROSAR OBD协议栈解决方案及其应用

    内容概要:本文详细介绍了VECTOR提供的MICROSAR OBD协议栈解决方案,涵盖了OBD模块、ECU支持、监控功能和服务请求等方面的内容。此外,还讨论了OBD在不同国家和地区的技术标准与法规要求,以及MICROSAR OBD解决方案的优势,如适应不同项目的需求和高度集成于AUTOSAR 4平台。 适合人群:汽车电子工程师、软件开发者、汽车制造商及相关行业从业人员。 使用场景及目标:① 适用于车辆诊断系统的开发和维护;②帮助工程师理解和掌握OBD协议的具体实施方法和应用场景;③ 提供了一个成熟、可扩展的解决方案,用于满足OBD相关标准和法规的要求。 其他说明:本文不仅提供了技术层面的详细解析,还探讨了实际操作过程中可能遇到的问题和解决方案。同时强调了屏蔽信息过载的重要性,提醒工程师保持内心平静,专注做好本职工作。

    适用于 Python 的 LINE 消息 API SDK.zip

    适用于 Python 的 LINE 消息 API SDK适用于 Python 的 LINE Messaging API 的 SDK。介绍适用于 Python 的 LINE Messaging API SDK 可以轻松使用 LINE Messaging API 开发机器人,您可以在几分钟内创建一个示例机器人。文档请参阅官方 API 文档了解更多信息英语https //developers.line.biz/en/docs/messaging-api/overview/日语https://developers.line.biz/ja/docs/messaging-api/overview/要求Python >= 3.9安装$ pip 安装 line-bot-sdk概要用法from flask import Flask, request, abortfrom linebot.v3 import ( WebhookHandler)from linebot.v3.exceptions import ( InvalidSig

    Java字节码工程工具包.zip

    Java字节码工程工具包Javassist 版本 3版权所有 (C) 1999-2023 Shigeru Chiba,保留所有权利。Javassist(JAVA 编程助手)使 Java 字节码操作变得简单。它是一个用于编辑 Java 字节码的类库它使 Java 程序能够在运行时定义新类并在 JVM 加载类文件时对其进行修改。与其他类似的字节码编辑器不同,Javassist 提供两个级别的 API源代码级别和字节码级别。如果用户使用源代码级别 API,他们可以编辑类文件而无需了解 Java 字节码的规范。整个 API 仅使用 Java 语言的词汇表进行设计。您甚至可以以源文本的形式指定插入的字节码Javassist 会即时编译它。另一方面,字节码级别 API 允许用户像其他编辑器一样直接编辑类文件。该软件根据 Mozilla 公共许可证版本 1.1、GNU 宽通用公共许可证版本 2.1 或更高版本或 Apache 许可证版本 2.0 分发。文件README.md 此自述文件。Changes.md 发行说明。License.html 许可证文件。tuto

    毕设源码-基于python的西西家居全屋定制系统的设计与实现_ijsj--论文-期末大作业+说明文档.rar

    本项目是基于Python语言开发的西西家居全屋定制系统,旨在为家居行业提供一个高效、智能的定制解决方案。项目涵盖了从客户需求分析、设计方案生成、材料选购到最终订单生成的全过程,力求实现家居定制的数字化和智能化。 在主要功能方面,系统具备强大的客户管理模块,能够详细记录和分析客户的定制需求。设计模块则采用先进的三维建模技术,为客户提供直观、真实的家居设计方案预览。此外,系统还整合了丰富的材料数据库,方便客户根据自身喜好和预算进行材料选择。 框架方面,项目采用了B/S架构,确保了系统的稳定性和可扩展性。后端使用Python的Django框架,前端则结合了HTML、CSS和JavaScript等技术,实现了用户界面的友好和响应速度。 开发此项目的目的,不仅是为了满足家居行业对个性化定制的需求,也为计算机相关专业的学生提供了一个实践和学习的平台,有助于提升他们的实际开发能力。

    Javascript 是数字化创新的起点,是语言的基础,也是基本概念 .zip

    Javascript 是数字化创新的起点,是语言的基础,也是基本概念。Basecamp JavascriptJavascript 是数字化创新的起点,是语言的基础,也是基本概念。嵌套存储库,可作为启动项下待办事项的实践活动。

    已弃用 - Coinbase Python API.zip

    已弃用 — Coinbase Python APICoinbase Coinbase API V2的官方 Python 库。重要提示此库当前针对的是 API V2,而 OAuth 客户端需要 V2 权限(即wallet:accounts:read)。如果您仍在使用 API V1,请使用此库的旧版本。特征接近 100% 的测试覆盖率。支持API Key + Secret和OAuth 2身份验证。调用 API 的便捷方法 - 为您打包 JSON!自动将 API 响应解析为相关的 Python 对象。使用IPython时,所有对象都具有可制表完成的方法和属性。安装coinbase可以在PYPI上使用。使用以下命令安装pippip install coinbase或者easy_installeasy_install coinbase该库目前针对 Python 版本 2.7 和 3.4+ 进行了测试。注意此软件包名称过去是指George Sibble维护的非官方 coinbase_python 库。George 慷慨地允许我们使用此软件包

    基于RBAC权限控制的基础后台.zip

    基于RBAC权限控制的基础后台

    毕设源码-python-基于Python爬虫的网络小说数据分析系统的设计与实现-期末大作业+说明文档.rar

    本项目是基于Python爬虫的网络小说数据分析系统的设计与实现,旨在为计算机相关专业的大学生提供一个实践平台,特别是在毕业设计和项目实战练习方面。项目通过Python强大的网络爬虫技术,从流行的网络小说网站自动抓取数据,包括书籍信息、章节内容、用户评论等。 主要功能涵盖数据采集、数据清洗、数据存储和数据分析。数据采集模块利用Scrapy等爬虫框架高效抓取网页内容;数据清洗模块确保数据的准确性和一致性;数据存储则采用MySQL等数据库系统,便于数据管理和查询;数据分析模块通过Pandas、NumPy等工具进行数据处理和分析,生成多维度的统计报告和可视化图表。 此项目不仅帮助学生掌握Python编程和网络爬虫技术,还能让他们深入了解数据分析的全过程,提升解决实际问题的能力。同时,系统的实现和应用也反映了现代信息技术在文学创作和消费领域的应用价值和潜力。

    ssm框架Java项目源码-基于Java的在线日语培训平台的设计与实现+jsp毕设-大作业.zip

    本项目是一个基于Java的在线日语培训平台的设计与实现,采用SSM框架(Spring+SpringMVC+MyBatis)进行开发,旨在为计算机相关专业的学生提供一个实践和学习的平台,同时也为日语学习者提供一个在线学习的空间。项目中主要功能涵盖了用户管理、课程管理、学习资源上传下载、在线测试与反馈等多个方面。通过该平台,教师能够轻松管理课程内容和学生信息,学生则可以随时随地访问学习资源,参与在线课程和测试,从而提高学习效率和兴趣。 在开发此项目的过程中,我们重点关注了系统的可维护性和可扩展性,确保代码结构清晰,便于后续的功能迭代和优化。此外,通过使用SSM框架,实现了前后端的分离,提高了开发效率和系统的响应速度。该项目不仅能够满足毕设的需求,还能作为Java学习者提升编程能力和实践经验的实用工具。

    基于java的机票管理系统设计与实现.docx

    基于java的机票管理系统设计与实现.docx

    基于Java实现的数据结构设计源码学习指南

    该项目为《基于Java实现的数据结构设计源码》,共包含51个文件,主要由46个Java源文件构成,辅以2个文本文件、1个Git忽略文件、1个许可证文件以及1个XML文件,全面涵盖了数据结构设计的核心内容。

    绿色食品 水稻生产操作规程.docx

    绿色食品 水稻生产操作规程.docx

    这款出色的应用程序可以纠正您之前的控制台命令 .zip

    他妈的 Fuck是一款出色的应用程序,其灵感来自@liamosaur 的 推文,它可以纠正以前控制台命令中的错误。The Fuck太慢了吗?试试实验性的即时模式!更多示例➜ apt-get install vimE: Could not open lock file /var/lib/dpkg/lock - open (13: Permission denied)E: Unable to lock the administration directory (/var/lib/dpkg/), are you root?➜ fucksudo apt-get install vim [enter/↑/↓/ctrl+c][sudo] password for nvbn:Reading package lists... Done...➜ git pushfatal: The current branch master has no upstream branch.To push the current branch and set the remote

    全国大学生FPGA创新设计竞赛作品 泡罩包装药品质量在线检测平台.zip

    全国大学生FPGA创新设计竞赛作品 “泡罩包装药品质量在线检测平台“.zip

    桃苗木质量基本要求表.docx

    桃苗木质量基本要求表.docx

    使用 Python 漂亮地打印表格数据,这是一个库和一个命令行实用程序 存储库从 bitbucket.org,astanin,python-tabulate 迁移而来 .zip

    使用 Python 漂亮地打印表格数据,这是一个库和一个命令行实用程序。存储库从 bitbucket.org/astanin/python-tabulate 迁移而来。python-tabulate使用 Python、库和命令行实用程序漂亮地打印表格数据。该库的主要用例是轻松打印小表格只需一个函数调用,格式由数据本身引导为轻量级纯文本标记创作表格数据多种输出格式适合进一步编辑或转换混合文本和数字数据的可读表示智能列对齐、可配置数字格式、小数点对齐安装要安装 Python 库和命令行实用程序,请运行pip install tabulate命令行实用程序将在 Linux 上安装为(例如tabulate)或者在 Windows 上的 Python 安装中安装为(例如)。bin/usr/bintabulate.exeScriptsC:\Python39\Scripts\tabulate.exe您可以考虑仅为当前用户安装该库pip install tabulate --user在这种情况下,命令行实用程序将安装到 ~/.local/bin/tabula

Global site tag (gtag.js) - Google Analytics