浏览 5471 次
精华帖 (0) :: 良好帖 (0) :: 新手帖 (0) :: 隐藏帖 (0)
|
|
---|---|
作者 | 正文 |
发表时间:2007-09-03
如下面的代码 // 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 声明:ITeye文章版权属于作者,受法律保护。没有作者书面许可不得转载。
推荐链接
|
|
返回顶楼 | |
发表时间:2007-09-03
你前面一种方法可以compile过吗
这里的uname,pwd.. 都是jspservice方法里的栈变量 不传param在别的method里应该取不到的 |
|
返回顶楼 | |
发表时间:2007-09-03
确认第二种方法可行?定义为实例变量会有线程不安全,这与与是否引用无关
|
|
返回顶楼 | |
发表时间:2007-09-04
赫赫。想通了。
先在这里回答上面两位的问题。 1。代码绝对可以运行。 2。导致第一种问题(servlet线程池问题)的根源绝对是引用。 给大家提示下我们正常中一般给一个方法传个对象参数后,一般是在方法外面的到然后再处理。而对这个方法外面参数对象基本不会附值。引用(指针)方向就不会变。 今天忙。没时间说在清除。大家想想吧。很有意思。(和我们正常的class编码习惯不同) |
|
返回顶楼 | |
发表时间:2007-09-04
忘说了。如果说代码无法运行是由于美一行代码前面有隐藏的空格(此空格需要删除。这个是拷贝到网页上面的通病,所以要注意。)
|
|
返回顶楼 | |
发表时间:2007-09-04
不明白楼主为什么要定义静态变量 知道什么是静态变量吗?<%!和<%的区别吗? 另外,第二种方法你可以把延时放到前面这里再试一下 //为了突出并发问题,在这儿首先执行一个费时操作 int i =0; double sum = 0.0; while (i++ < 200000000) { sum += i; } showUserInfo(username,password,output); |
|
返回顶楼 | |
发表时间:2007-09-04
回楼上的。
我明白什么是全局变量。 如果您觉得可以不用全局变量,jsp中的方法内部怎么调用参数。(jsp的方法是全局的。而且必须是全局的) 当然这么写本身就是不推荐的。但我是看别人的东西引发的一些想法。重点不在我的前提条件怎么设置,而是结论。我的结论是错误的(我认为java参数是传得引用,原来认为:如果是引用则不能达到预期的效果。可实际中达到了。^.^!!!!很绕口,不知道说清楚了没。) 大家如果对代码由疑问,请测试一下。 。 |
|
返回顶楼 | |
发表时间:2007-09-05
xianyun 写道 不明白楼主为什么要定义静态变量 知道什么是静态变量吗?<%!和<%的区别吗? 另外,第二种方法你可以把延时放到前面这里再试一下 //为了突出并发问题,在这儿首先执行一个费时操作 int i =0; double sum = 0.0; while (i++ < 200000000) { sum += i; } showUserInfo(username,password,output); <%!%>之内的并不是静态变量 只是一个实例变量。 |
|
返回顶楼 | |
发表时间: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已经完全没有关系了。 |
|
返回顶楼 | |
发表时间: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已经完全没有关系了。 我想了半天不知道怎么表达。 (可能跟本人有点懒有关系吧。本来想画个图,结果画了几个不满意。) 在这里只能说楼上的辛苦了。 |
|
返回顶楼 | |