servlet单例和线程安全实现

servlet/jsp 技术默认可以多线程模式运行。需要考虑多线程的安全问题。

servlet 体系结构是建立在 java 多线程机制上的。生命周期由 Web 容器负责的。
当客户端第一次访问该Servlet 时,Servlet 容器会根据 web.xml 文件实例化该servlet 类。
当有新的客户端请求该 servlet 时,一般不会再实例化该 servlet 类,也就是多个线程在使用该实例。
servlet 容器会自动根据线程池技术来支持系统的运行。


测试代码

Public class ConcurrentTest 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 ();   //不同的线程下,output 被修改覆盖了
        Try {
            Thread. sleep (5000); //为了突出并发问题,在这设置一个延时
        } Catch (Interrupted Exception e){}
        
        output.println("用户名:"+Username+"<BR>");
    }
}

a: http://localhost: 8080/servlet/ConcurrentTest? Username=a
b: http://localhost: 8080/servlet/ConcurrentTest? Username=b

java 内存模型

Java的内存模型JMM(Java Memory Model)JMM主要是为了规定了线程和内存之间的一些关系。根据JMM的设计,系统存在一个主内存(Main Memory),Java中所有实例变量都储存在主存中,对于所有线程都是共享的。每条线程都有自己的工作内存(Working Memory),工作内存由缓存和堆栈两部分组成,缓存中保存的是主存中变量的拷贝,缓存可能并不总和主存同步,也就是缓存中变量的修改可能没有立刻写到主存中;堆栈中保存的是线程的局部变量,线程之间无法相互直接访问堆栈中的变量。


b线程对实例变量output的修改覆盖了a线程对实例变量output的修改,从而导致了用户a的信息显示在了用户b的浏览器上。如果在a线程执行输出语句时,b线程对output的修改还没有刷新到主存,那么将不会出现图2所示的输出结果,因此这只是一种偶然现象,但这更增加了程序潜在的危险性。

如何才能实现servlet的线程安全
1、实现 SingleThreadModel  接口 -->
  该接口指定了系统如何处理对同一个Servlet的调用。如果一个Servlet被这个接口指定,那么在这个Servlet中的service方法将不会有两个线程被同时执行,当然也就不存在线程安全的问题。
2、使用同步代码块

public class ConcurrentTest extends HttpServlet {
    //使用同步代码块,来保证共享的实例变量的安全
    synchronized(this){
        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 ();   //不同的线程下,output 被修改覆盖了
        Try {
            Thread. sleep (5000); //为了突出并发问题,在这设置一个延时
        } Catch (Interrupted Exception e){}
        
        output.println("用户名:"+Username+"<BR>");
    }
}

3、避免使用示例变量
  将实例变量(全局变量、静态变量)该为局部变量。

如果一个Servlet实现了SingleThreadModel接口,Servlet引擎将为每个新的请求创建一个单独的Servlet实例,这将引起大量的系统开销。SingleThreadModel在Servlet2.4中已不再提倡使用。
如果在程序中使用同步来保护要使用的共享的数据,也会使系统的性能大大下降。因为被同步的代码块在同一时刻只能有一个线程执行它,使得其同时处理客户请求的吞吐量降低,而且很多客户处于阻塞状态。另外为保证主存内容和线程的工作内存中的数据的一致性,要频繁地刷新缓存,这也会大大地影响系统的性能。
所以在实际的开发中也应避免或最小化 Servlet 中的同步代码;在Serlet中避免使用实例变量是保证Servlet线程安全的最佳选择。
从Java 内存模型可知,方法中的临时变量是在栈上分配空间,而且每个线程都有自己私有的栈空间,所以它们不会影响线程的安全。

小结:

Servlet的线程安全问题只有在大量的并发访问时才会显现出来,并且很难发现,因此在编写Servlet程序时要特别注意。线程安全问题主要是由实例变量造成的,因此在Servlet中应避免使用实例变量。如果应用程序设计无法避免使用实例变量,那么使用同步来保护要使用的实例变量,但为保证系统的最佳性能,应该同步可用性最小的代码路径。

 
SpringMVC的contrioller 是单实例的,怎么保证线程安全
SpringMVC是基于方法的拦截
Struts2是基于类的拦截
对于Struts2来说,因为每次处理一个请求,struts就会实例化一个对象;这样就不会有线程安全的问题
Spring的controller默认是Singleton的,这意味着每一个request过来,系统都会用原有的instance去处理:
ThreadLocal是为了保证线程安全,实际上ThreadLoacal的key就是当前线程的Thread实例。单例模式下,spring把每个线程可能存在线程安全问题的参数值放进了ThreadLocal。这样虽然是一个实例在操作,但是不同线程下的数据互相之间都是隔离的。

posted @ 2019-03-27 14:16  payn  阅读(242)  评论(0)    收藏  举报