写了一个Servlet,用到了一个实例变量的HashMap对象,才发现,当第一次访问了该Servlet后,再次访问的时候,HashMap里是有值的,出于好奇,做了个测试,发现当第一次访问Servlet的时候,执行顺序是:调用默认构造方法-->Init()由容器实例化Servlet-->doGet()/doPost()响应请求, 当再次访问这个Servlet的时候,就只会执行doGet()/doPost()了,还是用的原来的那个实例,所以实例变量里有值是正常的, 只有当WebService(例如Tomcat)关闭的时候或Reloading的时候(即覆盖CLASS文件),Servlet才会去调用destroy()方法去销毁实例,此时再去访问则需要重新init().
查了点资料发现:Servlet体系结构是建立在Java多线程机制之上的,它的生命周期是由Web容器负责的。当客户端第一次请求某个Servlet时,Servlet容器将会根据web.xml配置文件实例化这个Servlet类。当有新的客户端请求该Servlet时,一般不会再实例化该Servlet类,也就是有多个线程在使用这个实例。Servlet容器会自动使用线程池等技术来支持系统的运行,如图1所示。
![深入研究Servlet线程安全性问题](http://www.qqread.com/ArtImage/20051121/30_1.jpg)
图1 Servlet线程池
这样,当两个或多个线程同时访问同一个Servlet时,可能会发生多个线程同时访问同一资源的情况,数据可能会变得不一致。所以在用Servlet构建的Web应用时如果不注意线程安全的问题,会使所写的Servlet程序有难以发现的错误。
String instanceVariable;
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException
{
instanceVariable = request.getParameter("Username");
try{
Thread.sleep (5000);
}
catch(Exception e){}
response.getWriter().write(instanceVariable);
response.getWriter().close();
}
![深入研究Servlet线程安全性问题](http://www.qqread.com/ArtImage/20051121/30_3.jpg)
图3 Servlet实例的JMM模型
下面根据图3所示的内存模型,来分析当用户a和b的线程(简称为a线程、b线程)并发执行时,Servlet实例中所涉及变量的变化情况及线程的执行情况,如图4所示。
调度时刻 | a线程 | b线程 |
T1 | 访问Servlet页面 | |
T2 | 访问Servlet页面 | |
T3 | output=a的输出username=a休眠5000毫秒,让出CPU | |
T4 | output=b的输出(写回主存)username=b休眠5000毫秒,让出CPU | |
T5 | 在用户b的浏览器上输出a线程的username的值,a线程终止。 | |
T6 | 在用户b的浏览器上输出b线程的username的值,b线程终止。 |
图4 Servlet实例的线程调度情况
我自己试了下,发现真的会发生这种情况,即a浏览器输入Username=a,最后却输出了b,由此可见Servlet的线程是不安全的,为了避免这种不安全的事发生,所以在Servlet里不要使用实例变量或使用synchronized关键字(需牺牲性能,不推荐),除非是实例变量不参加计算或显示(例如 数据库连接或日志对象).