﻿<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/"><channel><title>BlogJava-敬的世界-随笔分类-Servlet/Jsp</title><link>http://www.blogjava.net/nikita/category/41654.html</link><description /><language>zh-cn</language><lastBuildDate>Fri, 11 Sep 2009 14:58:09 GMT</lastBuildDate><pubDate>Fri, 11 Sep 2009 14:58:09 GMT</pubDate><ttl>60</ttl><item><title>在Servlet/JSP中的几种变量类型</title><link>http://www.blogjava.net/nikita/archive/2009/09/11/294784.html</link><dc:creator>picture talk</dc:creator><author>picture talk</author><pubDate>Fri, 11 Sep 2009 12:30:00 GMT</pubDate><guid>http://www.blogjava.net/nikita/archive/2009/09/11/294784.html</guid><wfw:comment>http://www.blogjava.net/nikita/comments/294784.html</wfw:comment><comments>http://www.blogjava.net/nikita/archive/2009/09/11/294784.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/nikita/comments/commentRss/294784.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/nikita/services/trackbacks/294784.html</trackback:ping><description><![CDATA[
		<h3>源自 :　<a href="http://www.javaresearch.org/article/8465.htm">http://www.javaresearch.org/article/8465.htm</a></h3>
		<h3>一、在Servlet/JSP中的几种变量类型</h3>
		<br />在编写Servlet/JSP程序时，对实例变量一定要小心使用。因为实例变量是非线程安全的。<br />在Servlet/JSP中，变量可以归为下面的几类：<br /><br />1. 类变量<br />request，response，session，config，application，以及JSP页面内置的page, pageContext。<br />其中除了application外，其它都是线程安全的。<br /><br />2. 实例变量<br />实例变量是实例所有的，在堆中分配。在Servlet/JSP容器中，一般仅实例化一个Servlet/JSP实例，<br />启动多个该实例的线程来处理请求。而实例变量是该实例所有的线程所共享，所以，实例变量不是线程安全的。<br /><br />3. 局部变量<br />局部变量在堆栈中分配，因为每一个线程有自己的执行堆栈，所以，局部变量是线程安全的。<br /><br /><h3>二、在Servlet/JSP中的多线程同步问题</h3><br />在JSP中，使用实例变量要特别谨慎。首先请看下面的代码：<br /><div class="codeStyle"><ol><li><i><font color="#339900">// instanceconcurrenttest.jsp</font></i></li><li>&lt;%@ page contentType=<font color="#ff33ff">"text/html;charset=GBK"</font> %&gt; 
</li><li></li><li>&lt;%! 
</li><li>    <i><font color="#339900">//定义实例变量</font></i></li><li>    <b><a href="http://www.javaresearch.org/source/jdk142/java/lang/String.java.html" target="_blank"><font class="classLink"><u>String</u></font></a></b> username; 
</li><li>    <b><a href="http://www.javaresearch.org/source/jdk142/java/lang/String.java.html" target="_blank"><font class="classLink"><u>String</u></font></a></b> password; 
</li><li></li><li>    java.io.<b><a href="http://www.javaresearch.org/source/jdk142/java/io/PrintWriter.java.html" target="_blank"><font class="classLink"><u>PrintWriter</u></font></a></b> output; 
</li><li></li><li>%&gt; 
</li><li>&lt;% 
</li><li></li><li>    <i><font color="#339900">//从request中获取参数</font></i></li><li>    username = request.getParameter(<font color="#ff33ff">"username"</font>); 
</li><li>    password = request.getParameter(<font color="#ff33ff">"password"</font>); 
</li><li></li><li>    output = response.getWriter(); 
</li><li></li><li>    showUserInfo();     
</li><li></li><li>%&gt; 
</li><li></li><li>&lt;%! 
</li><li>    <b><font color="#0000ff">public</font></b> <b><font color="#0000ff">void</font></b> showUserInfo() { 
</li><li>        <i><font color="#339900">//为了突出并发问题，在这儿首先执行一个费时操作</font></i></li><li>        <b><font color="#0000ff">int</font></b> i =0; 
</li><li>        <b><font color="#0000ff">double</font></b> sum = 0.0; 
</li><li>        <b><font color="#0000ff">while</font></b> (i++ &lt; 200000000) { 
</li><li>            sum += i; 
</li><li>        } 
</li><li>         
</li><li>        output.println(<b><a href="http://www.javaresearch.org/source/jdk142/java/lang/Thread.java.html" target="_blank"><font class="classLink"><u>Thread</u></font></a></b>.currentThread().getName() + <font color="#ff33ff">"&lt;br&gt;"</font>); 
</li><li>        output.println(<font color="#ff33ff">"username:"</font> + username + <font color="#ff33ff">"&lt;br&gt;"</font>); 
</li><li>        output.println(<font color="#ff33ff">"password:"</font> + password + <font color="#ff33ff">"&lt;br&gt;"</font>); 
</li><li>    } 
</li><li>%&gt; </li></ol></div>在这个页面中，首先定义了两个实例变量，username和password。然后在从request中获取这两个参数，并调用showUserInfo()方法将请求用户的信息回显在该客户的浏览器上。在一个用户访问是，不存在问题。但在多个用户并发访问时，就会出现其它用户的信息显示在另外一些用户的浏览器上的问题。这是一个严重的问题。为了突出并发问题，便于测试、观察，我们在回显用户信息时执行了一个模拟的费时操作，比如，下面的两个用户同时访问（可以启动两个IE浏览器，或者在两台机器上同时访问）：<br />a：    <a href="http://localhost:8080/instanceconcurrenttest.jsp?username=a&amp;password=123">http://localhost:8080/instanceconcurrenttest.jsp?username=a&amp;password=123</a><br />b：    <a href="http://localhost:8080/instanceconcurrenttest.jsp?username=b&amp;password=456">http://localhost:8080/instanceconcurrenttest.jsp?username=b&amp;password=456</a><br /><br />如果a点击链接后，b再点击链接，那么，a将返回一个空白屏幕，b则得到a以及b两个线程的输出。请看下面的屏幕截图：<br /><img src="http://www.javaresearch.org/members/srx81/images/instanceconcurrenttest1.gif" /> <br />图1：a的屏幕<br /><img src="http://www.javaresearch.org/members/srx81/images/instanceconcurrenttest2.gif" /> <br />图2：b的屏幕<br /><br />从运行结果的截图上可以看到，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的取代。请参加下图所示：<br /><br /><img src="http://www.javaresearch.org/members/srx81/images/instanceconcurrenttest3.gif" /> <br />图3：a、b两个线程的时间线<br /><br />而实际程序中，由于设置实例变量，使用实例变量这两个时间点非常接近，所以，像本例的同步问题并没有这么突出，可能会偶尔出现，但这却更加具有危险性，也更加难于调试。<br /><br />同样，对于Servlet也存在实例变量的多线程问题，请看上面页面的Servlet版：<br /><div class="codeStyle"><ol><li><i><font color="#339900">// InstanceConcurrentTest.java</font></i></li><li><b><font color="#0000ff">import</font></b> javax.servlet.*; 
</li><li><b><font color="#0000ff">import</font></b> javax.servlet.http.*; 
</li><li><b><font color="#0000ff">import</font></b> java.io.<b><a href="http://www.javaresearch.org/source/jdk142/java/io/PrintWriter.java.html" target="_blank"><font class="classLink"><u>PrintWriter</u></font></a></b>; 
</li><li></li><li></li><li><b><font color="#0000ff">public</font></b> <b><font color="#0000ff">class</font></b> InstanceConcurrentTest <b><font color="#0000ff">extends</font></b> <a href="http://www.javaresearch.org/source/j2ee1.3.1/javax/servlet/http/HttpServlet.java.html" target="_blank"><font class="classLink"><u>HttpServlet</u></font></a>  
</li><li>{ 
</li><li>    <b><a href="http://www.javaresearch.org/source/jdk142/java/lang/String.java.html" target="_blank"><font class="classLink"><u>String</u></font></a></b> username; 
</li><li>    <b><a href="http://www.javaresearch.org/source/jdk142/java/lang/String.java.html" target="_blank"><font class="classLink"><u>String</u></font></a></b> password; 
</li><li></li><li>    <b><a href="http://www.javaresearch.org/source/jdk142/java/io/PrintWriter.java.html" target="_blank"><font class="classLink"><u>PrintWriter</u></font></a></b> out; 
</li><li></li><li>    <b><font color="#0000ff">public</font></b> <b><font color="#0000ff">void</font></b> doGet(<a href="http://www.javaresearch.org/source/j2ee1.3.1/javax/servlet/http/HttpServletRequest.java.html" target="_blank"><font class="classLink"><u>HttpServletRequest</u></font></a> request, <a href="http://www.javaresearch.org/source/j2ee1.3.1/javax/servlet/http/HttpServletResponse.java.html" target="_blank"><font class="classLink"><u>HttpServletResponse</u></font></a> response)  
</li><li>         <b><font color="#0000ff">throws</font></b> <a href="http://www.javaresearch.org/source/j2ee1.3.1/javax/servlet/ServletException.java.html" target="_blank"><font class="classLink"><u>ServletException</u></font></a>,java.io.<b><a href="http://www.javaresearch.org/source/jdk142/java/io/IOException.java.html" target="_blank"><font class="classLink"><u>IOException</u></font></a></b></li><li>    { 
</li><li>        <i><font color="#339900">//从request中获取参数</font></i></li><li>        username = request.getParameter(<font color="#ff33ff">"username"</font>); 
</li><li>        password = request.getParameter(<font color="#ff33ff">"password"</font>); 
</li><li>        <b><a href="http://www.javaresearch.org/source/jdk142/java/lang/System.java.html" target="_blank"><font class="classLink"><u>System</u></font></a></b>.out.println(<b><a href="http://www.javaresearch.org/source/jdk142/java/lang/Thread.java.html" target="_blank"><font class="classLink"><u>Thread</u></font></a></b>.currentThread().getName() +  
</li><li>                     <font color="#ff33ff">" | set username:"</font> + username); 
</li><li></li><li>        out = response.getWriter(); 
</li><li></li><li>        showUserInfo();     
</li><li>    } 
</li><li></li><li>    <b><font color="#0000ff">public</font></b> <b><font color="#0000ff">void</font></b> showUserInfo() { 
</li><li>        <i><font color="#339900">//为了突出并发问题，在这儿首先执行一个费时操作</font></i></li><li>        <b><font color="#0000ff">int</font></b> i =0; 
</li><li>        <b><font color="#0000ff">double</font></b> sum = 0.0; 
</li><li>        <b><font color="#0000ff">while</font></b> (i++ &lt; 200000000) { 
</li><li>            sum += i; 
</li><li>        } 
</li><li></li><li>        out.println(<font color="#ff33ff">"thread:"</font> + <b><a href="http://www.javaresearch.org/source/jdk142/java/lang/Thread.java.html" target="_blank"><font class="classLink"><u>Thread</u></font></a></b>.currentThread().getName()); 
</li><li>        out.println(<font color="#ff33ff">"username:"</font>+ username); 
</li><li>        out.println(<font color="#ff33ff">"password:"</font> + password); 
</li><li>    } 
</li><li></li><li>} </li></ol></div><br /><h3>三、解决方案</h3><br /><h4>1. 以单线程运行Servlet/JSP</h4><br />在JSP中，通过设置：&lt;%@ page isThreadSafe="false" %&gt;，在Servlet中，通过实现javax.servlet.SingleThreadModel，此时Web容器将保证JSP或Servlet实例以单线程方式运行。<br /><br /><b>重要提示：在测试中发现，Tomcat 4.1.17不能正确支持isThreadSafe属性，所以，指定isTheadSafe为false后，在Tomcat 4.1.17中仍然出现多线程问题，这是Tomcat 4.1.17的Bug。在Tomcat 3.3.1和Resin 2.1.5中测试通过。</b><br /><br /><h4>2. 去除实例变量，通过参数传递</h4><br />从上面的分析可见，应该在Servlet/JSP中尽量避免使用实例变量。比如，下面的修正代码，去除了实例变量，通过定义局部变量，并参数进行传递。这样，由于局部变量是在线程的堆栈中进行分配的，所以是线程安全的。不会出现多线程同步的问题。代码如下：<br /><div class="codeStyle"><ol><li>&lt;%@ page contentType=<font color="#ff33ff">"text/html;charset=GBK"</font> %&gt; 
</li><li></li><li>&lt;% 
</li><li>    <i><font color="#339900">//使用局部变量</font></i></li><li>    <b><a href="http://www.javaresearch.org/source/jdk142/java/lang/String.java.html" target="_blank"><font class="classLink"><u>String</u></font></a></b> username; 
</li><li>    <b><a href="http://www.javaresearch.org/source/jdk142/java/lang/String.java.html" target="_blank"><font class="classLink"><u>String</u></font></a></b> password; 
</li><li></li><li>    java.io.<b><a href="http://www.javaresearch.org/source/jdk142/java/io/PrintWriter.java.html" target="_blank"><font class="classLink"><u>PrintWriter</u></font></a></b> output; 
</li><li></li><li>    <i><font color="#339900">//从request中获取参数</font></i></li><li>    username = request.getParameter(<font color="#ff33ff">"username"</font>); 
</li><li>    password = request.getParameter(<font color="#ff33ff">"password"</font>); 
</li><li></li><li>    output = response.getWriter(); 
</li><li></li><li>    showUserInfo(output, username, password);     
</li><li></li><li>%&gt; 
</li><li></li><li>&lt;%! 
</li><li>    <b><font color="#0000ff">public</font></b> <b><font color="#0000ff">void</font></b> showUserInfo(java.io.<b><a href="http://www.javaresearch.org/source/jdk142/java/io/PrintWriter.java.html" target="_blank"><font class="classLink"><u>PrintWriter</u></font></a></b> _output,  
</li><li>         <b><a href="http://www.javaresearch.org/source/jdk142/java/lang/String.java.html" target="_blank"><font class="classLink"><u>String</u></font></a></b> _username, <b><a href="http://www.javaresearch.org/source/jdk142/java/lang/String.java.html" target="_blank"><font class="classLink"><u>String</u></font></a></b> _password) { 
</li><li>        <i><font color="#339900">//为了突出并发问题，在这儿首先执行一个费时操作</font></i></li><li>        <b><font color="#0000ff">int</font></b> i =0; 
</li><li>        <b><font color="#0000ff">double</font></b> sum = 0.0; 
</li><li>        <b><font color="#0000ff">while</font></b> (i++ &lt; 200000000) { 
</li><li>            sum += i; 
</li><li>        } 
</li><li></li><li>         
</li><li>        _output.println(<b><a href="http://www.javaresearch.org/source/jdk142/java/lang/Thread.java.html" target="_blank"><font class="classLink"><u>Thread</u></font></a></b>.currentThread().getName() + <font color="#ff33ff">"&lt;br&gt;"</font>); 
</li><li>        _output.println(<font color="#ff33ff">"username:"</font> + _username + <font color="#ff33ff">"&lt;br&gt;"</font>); 
</li><li>        _output.println(<font color="#ff33ff">"password:"</font> + _password + <font color="#ff33ff">"&lt;br&gt;"</font>); 
</li><li>    } 
</li><li>%&gt; </li></ol></div><br />注：有的资料上指出在printUserInfo()方法或者实例变量的相关操作语句上使用synchronized关键字进行同步，但这样并不能解决多线程的问题。因为，这样虽然可以使对实例变量的操作代码进行同步，但并不能阻止一个线程使用另外一个线程修改后的“脏的”实例变量。所以，除了降低运行效率外，不会起到预期效果。<br /><br /><img src ="http://www.blogjava.net/nikita/aggbug/294784.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/nikita/" target="_blank">picture talk</a> 2009-09-11 20:30 <a href="http://www.blogjava.net/nikita/archive/2009/09/11/294784.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item></channel></rss>