关于Servlet的线程不安全的原因及防范方法

    写了一个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所示。


图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();
}

图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关键字(需牺牲性能,不推荐),除非是实例变量不参加计算或显示(例如 数据库连接或日志对象).

参考文章:http://www.99inf.net/softwaredev/java/52247.htm

http://www.99inf.net/softwaredev/java/52247.htm

你可能感兴趣的:(J2SE,servlet,hashmap,webservice,浏览器,exception,output)