Servlet/JSP技术和ASPPHP等相比由于其多线程运行而具有很高的执行效率由于Servlet/JSP默认是以多线程模式执行的所以在编写代码时需要非常细致地考虑多线程的安全性问题然而很多人编写Servlet/JSP程序时并没有注意到多线程安全性的问题这往往造成编写的程序在少量用户访问时没有任何问题而在并发用户上升到一定值时就会经常出现一些莫明其妙的问题
Servlet的多线程机制
Servlet体系结构是建立在Java多线程机制之上的它的生命周期是由Web容器负责的当客户端第一次请求某个Servlet时Servlet容器将会根据webxml配置文件实例化这个Servlet类当有新的客户端请求该Servlet时一般不会再实例化该Servlet类也就是有多个线程在使用这个实例Servlet容器会自动使用线程池等技术来支持系统的运行如图所示
图 Servlet线程池这样当两个或多个线程同时访问同一个Servlet时可能会发生多个线程同时访问同一资源的情况数据可能会变得不一致所以在用Servlet构建的Web应用时如果不注意线程安全的问题会使所写的Servlet程序有难以发现的错误
Servlet的线程安全问题
Servlet的线程安全问题主要是由于实例变量使用不当而引起的这里以一个现实的例子来说明
Import javaxservlet *;
Import javaxservlethttp *;
Import javaio *;
Public class Concurrent Test extends HttpServlet {PrintWriter output;
Public void service (HttpServletRequest request
HttpServletResponse response) throws ServletException IOException {String username;
ResponsesetContentType (text/html; charset=gb);
Username = requestgetParameter (username);
Output = responsegetWriter ();
Try {Thread sleep (); //为了突出并发问题在这设置一个延时
} Catch (Interrupted Exception e){}
outputprintln(用户名:+Username+<BR>);
}
}
该Servlet中定义了一个实例变量output在service方法将其赋值为用户的输出当一个用户访问该Servlet时程序会正常的运行但当多个用户并发访问时就可能会出现其它用户的信息显示在另外一些用户的浏览器上的问题这是一个严重的问题为了突出并发问题便于测试观察我们在回显用户信息时执行了一个延时的操作假设已在webxml配置文件中注册了该Servlet现有两个用户a和b同时访问该Servlet(可以启动两个IE浏览器或者在两台机器上同时访问)即同时在浏览器中输入
a//localhost: /servlet/ConcurrentTest? Username=a
b//localhost: /servlet/ConcurrentTest? Username=b
如果用户b比用户a回车的时间稍慢一点将得到如图所示的输出
educitycn/img_///jpg>图 a用户和b用户的浏览器输出从图中可以看到Web服务器启动了两个线程分别处理来自用户a和用户b的请求但是在用户a的浏览器上却得到一个空白的屏幕用户a的信息显示在用户b的浏览器上该Servlet存在线程不安全问题下面我们就从分析该实例的内存模型入手观察不同时刻实例变量output的值来分析使该Servlet线程不安全的原因
Java的内存模型JMM(Java Memory Model)JMM主要是为了规定了线程和内存之间的一些关系根据JMM的设计系统存在一个主内存(Main Memory)Java中所有实例变量都储存在主存中对于所有线程都是共享的每条线程都有自己的工作内存(Working Memory)工作内存由缓存和堆栈两部分组成缓存中保存的是主存中变量的拷贝缓存可能并不总和主存同步也就是缓存中变量的修改可能没有立刻写到主存中堆栈中保存的是线程的局部变量线程之间无法相互直接访问堆栈中的变量根据JMM我们可以将论文中所讨论的Servlet实例的内存模型抽象为图所示的模型
educitycn/img_///jpg>图 Servlet实例的JMM模型下面根据图所示的内存模型来分析当用户a和b的线程(简称为a线程b线程)并发执行时Servlet实例中所涉及变量的变化情况及线程的执行情况如图所示
educitycn/img_///gif>图 Servlet实例的线程调度情况从图中可以清楚的看到由于b线程对实例变量output的修改覆盖了a线程对实例变量output的修改从而导致了用户a的信息显示在了用户b的浏览器上如果在a线程执行输出语句时b线程对output的修改还没有刷新到主存那么将不会出现图所示的输出结果因此这只是一种偶然现象但这更增加了程序潜在的危险性