??xml version="1.0" encoding="utf-8" standalone="yes"?> 设计U程安全的Servlet
关键字:Servlet U程安全 同步 Java内存模型 实例变量
Servlet/JSP技术和ASP、PHP{相比,׃其多U程q行而具有很高的执行效率。由于Servlet/JSP默认是以多线E模式执行的Q所以,在编写代码时需要非常细致地考虑多线E的安全性问题。然而,很多人编写Servlet/JSPE序时ƈ没有注意到多U程安全性的问题Q这往往造成~写的程序在量用户讉K时没有Q何问题,而在q发用户上升C定值时Q就会经常出C些莫明其妙的问题?br />
Servlet的多U程机制
Servlet体系l构是徏立在Java多线E机制之上的Q它的生命周期是由Web容器负责的。当客户端第一ơ请求某个ServletӞServlet容器会Ҏweb.xml配置文g实例化这个ServletcR当有新的客Lh该ServletӞ一般不会再实例化该Servletc,也就是有多个U程在用这个实例。Servlet容器会自动用线E池{技术来支持pȝ的运行,如图1所C?br />
? ServletU程?/td>
q样Q当两个或多个线E同时访问同一个ServletӞ可能会发生多个线E同时访问同一资源的情况,数据可能会变得不一致。所以在用Servlet构徏的Web应用时如果不注意U程安全的问题,会所写的ServletE序有难以发现的错误?br />
Servlet的线E安全问?br />
Servlet的线E安全问题主要是׃实例变量使用不当而引LQ这里以一个现实的例子来说明?br />
Import javax.servlet. *;
Import javax.servlet.http. *;
Import java.io. *;
Public class Concurrent Test extends HttpServlet {PrintWriter output;
Public void service (HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException {String username;
Response.setContentType ("text/html; charset=gb2312");
Username = request.getParameter ("username");
Output = response.getWriter ();
Try {Thread. sleep (5000); //ZH出q发问题Q在q设|一个g?br />} Catch (Interrupted Exception e){}
output.println("用户?"+Username+"<BR>");
}
}
该Servlet中定义了一个实例变量outputQ在serviceҎ其赋gؓ用户的输出。当一个用戯问该ServletӞE序会正常的q行Q但当多个用户ƈ发访问时Q就可能会出现其它用L信息昄在另外一些用L览器上的问题。这是一个严重的问题。ؓ了突出ƈ发问题,便于试、观察,我们在回昄户信息时执行了一个g时的操作。假讑ַ在web.xml配置文g中注册了该ServletQ现有两个用户a和b同时讉K该ServletQ可以启动两个IE览器,或者在两台机器上同时访问),卛_时在览器中输入Q?br />
aQ?http://localhost: 8080/servlet/ConcurrentTest? Username=a
bQ?http://localhost: 8080/servlet/ConcurrentTest? Username=b
如果用户b比用户a回R的时间稍慢一点,得到如?所C的输出Q?br />
? a用户和b用户的浏览器输出
从图2中可以看刎ͼWeb服务器启动了两个U程分别处理来自用户a和用户b的请求,但是在用户a的浏览器上却得到一个空白的屏幕Q用户a的信息显C在用户b的浏览器上。该Servlet存在U程不安全问题。下面我们就从分析该实例的内存模型入?观察不同时刻实例变量output的值来分析使该ServletU程不安全的原因?br />
Java的内存模型JMMQJava Memory ModelQJMM主要是ؓ了规定了U程和内存之间的一些关pR根据JMM的设计,pȝ存在一个主内存(Main Memory)QJava中所有实例变量都储存在主存中Q对于所有线E都是共享的。每条线E都有自q工作内存(Working Memory)Q工作内存由~存和堆栈两部分l成Q缓存中保存的是d中变量的拯Q缓存可能ƈ不dd同步Q也是~存中变量的修改可能没有立刻写到d中;堆栈中保存的是线E的局部变量,U程之间无法怺直接讉K堆栈中的变量。根据JMMQ我们可以将论文中所讨论的Servlet实例的内存模型抽象ؓ?所C的模型?br />
? Servlet实例的JMM模型
下面Ҏ?所C的内存模型Q来分析当用户a和b的线E(UCؓaU程、bU程Qƈ发执行时QServlet实例中所涉及变量的变化情况及U程的执行情况,如图4所C?br />
? Servlet实例的线E调度情?br />
调度时刻
aU程
bU程
T1
讉KServlet面
T2
讉KServlet面
T3
output=a的输出username=a休眠5000毫秒Q让出CPU
T4
output=b的输出(写回dQusername=b休眠5000毫秒Q让出CPU
T5
在用户b的浏览器上输出aU程的username的?aU程l止?/td>
T6
在用户b的浏览器上输出bU程的username的?bU程l止?/td>
从图4中可以清楚的看到Q由于bU程对实例变量output的修改覆盖了aU程对实例变量output的修改,从而导致了用户a的信息显C在了用户b的浏览器上。如果在aU程执行输出语句ӞbU程对output的修改还没有hC存,那么不会出现图2所C的输出l果Q因此这只是一U偶然现象,但这更增加了E序潜在的危险性?span>
通过上面的分析,我们知道了实例变量不正确的用是造成ServletU程不安全的主要原因。下面针对该问题l出了三U解x案ƈҎ案的选取l出了一些参考性的?br />
1、实?SingleThreadModel 接口
该接口指定了pȝ如何处理对同一个Servlet的调用。如果一个Servlet被这个接口指?那么在这个Servlet中的serviceҎ不会有两个U程被同时执行,当然也就不存在线E安全的问题。这U方法只要将前面的Concurrent Testcȝcd定义更改为:
Public class Concurrent Test extends HttpServlet implements SingleThreadModel {
…………
}
2、同步对׃n数据的操?br />
使用synchronized 关键字能保证一ơ只有一个线E可以访问被保护的区D,在本论文中的Servlet可以通过同步块操作来保证U程的安全。同步后的代码如下:
………… Public class Concurrent Test extends HttpServlet { ………… Username = request.getParameter ("username"); Synchronized (this){ Output = response.getWriter (); Try { Thread. Sleep (5000); } Catch (Interrupted Exception e){} output.println("用户?"+Username+"<BR>"); } } } |
3、避免用实例变?br />
本实例中的线E安全问题是由实例变量造成的,只要在Servlet里面的Q何方法里面都不用实例变量,那么该Servlet是U程安全的?br />
修正上面的Servlet代码Q将实例变量改ؓ局部变量实现同L功能Q代码如下:
…… Public class Concurrent Test extends HttpServlet {public void service (HttpServletRequest request, HttpServletResponse Response) throws ServletException, IOException { Print Writer output; String username; Response.setContentType ("text/html; charset=gb2312"); …… } } |
对上面的三种Ҏq行试Q可以表明用它们都能设计出线E安全的ServletE序。但是,如果一个Servlet实现了SingleThreadModel接口QServlet引擎ؓ每个新的h创徏一个单独的Servlet实例Q这引起大量的pȝ开销。SingleThreadModel在Servlet2.4中已不再提倡用;同样如果在程序中使用同步来保护要使用的共享的数据Q也会ɾpȝ的性能大大下降。这是因同步的代码块在同一时刻只能有一个线E执行它Q得其同时处理客户h的吞吐量降低Q而且很多客户处于d状态。另外ؓ保证d内容和线E的工作内存中的数据的一致性,要频J地h~存,q也会大大地影响pȝ的性能。所以在实际的开发中也应避免或最化 Servlet 中的同步代码Q在Serlet中避免用实例变量是保证ServletU程安全的最佳选择。从Java 内存模型也可以知道,Ҏ中的临时变量是在栈上分配I间Q而且每个U程都有自己U有的栈I间Q所以它们不会媄响线E的安全?br />
结
Servlet的线E安全问题只有在大量的ƈ发访问时才会昄出来Qƈ且很隑֏玎ͼ因此在编写ServletE序时要特别注意。线E安全问题主要是由实例变量造成?因此在Servlet中应避免使用实例变量。如果应用程序设计无法避免用实例变量,那么使用同步来保护要使用的实例变量,但ؓ保证pȝ的最x能Q应该同步可用性最的代码路径?/p>