Servlet 单例多线程

Servlet 单例多线程html


Servlet如何处理多个请求访问?java

Servlet容器默认是采用单实例多线程的方式处理多个请求的:web

1.当web服务器启动的时候(或客户端发送请求到服务器时),Servlet就被加载并实例化(只存在一个Servlet实例);spring

2.容器初始化化Servlet主要就是读取配置文件(例如tomcat,能够经过servlet.xml的<Connector>设置线程池中线程数目,初始化线程池经过web.xml,初始化每一个参数值等等。数据库

3.当请求到达时,Servlet容器经过调度线程(Dispatchaer Thread) 调度它管理下线程池中等待执行的线程(Worker Thread)给请求者;apache

4.线程执行Servlet的service方法;浏览器

5.请求结束,放回线程池,等待被调用;缓存

(注意:避免使用实例变量(成员变量),由于若是存在成员变量,可能发生多线程同时访问该资源时,都来操做它,照成数据的不一致,所以产生线程安全问题)tomcat


从上面能够看出:安全

第一:Servlet单实例,减小了产生servlet的开销;

第二:经过线程池来响应多个请求,提升了请求的响应时间;

第三:Servlet容器并不关心到达的Servlet请求访问的是不是同一个Servlet仍是另外一个Servlet,直接分配给它一个新的线程;若是是同一个Servlet的多个请求,那么Servlet的service方法将在多线程中并发的执行;

第四:每个请求由ServletRequest对象来接受请求,由ServletResponse对象来响应该请求;


Servlet/JSP技术和ASP、PHP等相比,因为其多线程运行而具备很高的执行效率。因为Servlet/JSP默认是以多线程模式执行的,因此,在编写代码时须要很是细致地考虑多线程的安全性问题。 


JSP的中存在的多线程问题: 

当客户端第一次请求某一个JSP文件时,服务端把该JSP编译成一个CLASS文件,并建立一个该类的实例,而后建立一个线程处理CLIENT端的请求。若是有多个客户端同时请求该JSP文件,则服务端会建立多个线程。每一个客户端请求对应一个线程。以多线程方式执行可大大下降对系统的资源需求,提升系统的并发量及响应时间. 


对JSP中可能用的的变量说明以下: 

实例变量: 实例变量是在堆中分配的,并被属于该实例的全部线程共享,因此不是线程安全的. 

JSP系统提供的8个类变量 

JSP中用到的OUT,REQUEST,RESPONSE,SESSION,CONFIG,PAGE,PAGECONXT是线程安全的(由于每一个线程对应的request,respone对象都是不同的,不存在共享问题), APPLICATION在整个系统内被使用,因此不是线程安全的. 


局部变量: 局部变量在堆栈中分配,由于每一个线程都有它本身的堆栈空间,因此是线程安全的. 

静态类: 静态类不用被实例化,就可直接使用,也不是线程安全的. 


外部资源: 在程序中可能会有多个线程或进程同时操做同一个资源(如:多个线程或进程同时对一个文件进行写操做).此时也要注意同步问题. 


使它以单线程方式执行,这时,仍然只有一个实例,全部客户端的请求以串行方式执行。这样会下降系统的性能 


问题 

问题一. 说明其Servlet容器如何采用单实例多线程的方式来处理请求 

问题二. 如何在开发中保证servlet是单实例多线程的方式来工做(也就是说如何开发线程安全的servelt)。 


一. Servlet容器如何同时来处理多个请求 


Java的内存模型JMM(Java Memory Model) 

JMM主要是为了规定了线程和内存之间的一些关系。根据JMM的设计,系统存在一个主内存(Main Memory),Java中全部实例变量都储存在主存中,对于全部线程都是共享的。每条线程都有本身的工做内存(Working Memory),工做内存由缓存和堆栈两部分组成,缓存中保存的是主存中变量的拷贝,缓存可能并不总和主存同步,也就是缓存中变量的修改可能没有马上写到主存中;堆栈中保存的是线程的局部变量,线程之间没法相互直接访问堆栈中的变量。根据JMM,咱们能够将论文中所讨论的Servlet实例的内存模型抽象为图所示的模型。 


 


工做者线程Work Thread:执行代码的一组线程。 

调度线程Dispatcher Thread:每一个线程都具备分配给它的线程优先级,线程是根据优先级调度执行的。 


Servlet采用多线程来处理多个请求同时访问。servlet依赖于一个线程池来服务请求。线程池其实是一系列的工做者线程集合。Servlet使用一个调度线程来管理工做者线程。 


当容器收到一个Servlet请求,调度线程从线程池中选出一个工做者线程,将请求传递给该工做者线程,而后由该线程来执行Servlet的service方法。当这个线程正在执行的时候,容器收到另一个请求,调度线程一样从线程池中选出另外一个工做者线程来服务新的请求,容器并不关心这个请求是否访问的是同一个Servlet.当容器同时收到对同一个Servlet的多个请求的时候,那么这个Servlet的service()方法将在多线程中并发执行。 

Servlet容器默认采用单实例多线程的方式来处理请求,这样减小产生Servlet实例的开销,提高了对请求的响应时间,对于Tomcat能够在server.xml中经过<Connector>元素设置线程池中线程的数目。 


就实现来讲: 

调度者线程类所担负的责任如其名字,该类的责任是调度线程,只须要利用本身的属性完成本身的责任。因此该类是承担了责任的,而且该类的责任又集中到惟一的单体对象中。而其余对象又依赖于该特定对象所承担的责任,咱们就须要获得该特定对象。那该类就是一个单例模式的实现了。 



注意:服务器可使用多个实例来处理请求,代替单个实例的请求排队带来的效益问题。服务器建立一个Servlet类的多个Servlet实例组成的实例池,对于每一个请求分配Servlet实例进行响应处理,以后放回到实例池中等待下此请求。这样就形成并发访问的问题。 

此时,局部变量(字段)也是安全的,但对于全局变量和共享数据是不安全的,须要进行同步处理。而对于这样多实例的状况SingleThreadModel接口并不能解决并发访问问题。 SingleThreadModel接口在servlet规范中已经被废弃了。


二 如何开发线程安全的Servlet 

  一、实现 SingleThreadModel 接口 


  该接口指定了系统如何处理对同一个Servlet的调用。若是一个Servlet被这个接口指定,那么在这个Servlet中的service方法将不会有两个线程被同时执行,固然也就不存在线程安全的问题。这种方法只要将前面的Concurrent Test类的类头定义更改成: 


Public class Concurrent Test extends HttpServlet implements SingleThreadModel { 

………… 


  二、同步对共享数据的操做 


  使用synchronized 关键字能保证一次只有一个线程能够访问被保护的区段,在本论文中的Servlet能够经过同步块操做来保证线程的安全。同步后的代码以下: 


………… 

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>"); 


  三、避免使用实例变量 


  本实例中的线程安全问题是由实例变量形成的,只要在Servlet里面的任何方法里面都不使用实例变量,那么该Servlet就是线程安全的。 


  修正上面的Servlet代码,将实例变量改成局部变量实现一样的功能,代码以下: 


…… 

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"); 

…… 



 ** 对上面的三种方法进行测试,能够代表用它们都能设计出线程安全的Servlet程序。可是,若是一个Servlet实现了SingleThreadModel接口,Servlet引擎将为每一个新的请求建立一个单独的Servlet实例,这将引发大量的系统开销。SingleThreadModel在Servlet2.4中已再也不提倡使用;一样若是在程序中使用同步来保护要使用的共享的数据,也会使系统的性能大大降低。这是由于被同步的代码块在同一时刻只能有一个线程执行它,使得其同时处理客户请求的吞吐量下降,并且不少客户处于阻塞状态。另外为保证主存内容和线程的工做内存中的数据的一致性,要频繁地刷新缓存,这也会大大地影响系统的性能。因此在实际的开发中也应避免或最小化 Servlet 中的同步代码;在Serlet中避免使用实例变量是保证Servlet线程安全的最佳选择。从Java 内存模型也能够知道,方法中的临时变量是在栈上分配空间,并且每一个线程都有本身私有的栈空间,因此它们不会影响线程的安全。

更加详细的说明:


1,变量的线程安全:这里的变量指字段和共享数据(如表单参数值)。 

a,将 参数变量 本地化。多线程并不共享局部变量.因此咱们要尽量的在servlet中使用局部变量。 

例如:String user = ""; 

user = request.getParameter("user"); 


b,使用同步块Synchronized,防止可能异步调用的代码块。这意味着线程须要排队处理。在使用同板块的时候要尽量的缩小同步代码的范围,不要直接在sevice方法和响应方法上使用同步,这样会严重影响性能。 


2,属性的线程安全:ServletContext,HttpSession,ServletRequest对象中属性。 

ServletContext:(线程是不安全的) 

ServletContext是能够多线程同时读/写属性的,线程是不安全的。要对属性的读写进行同步处理或者进行深度Clone()。因此在Servlet上下文中尽量少许保存会被修改(写)的数据,能够采起其余方式在多个Servlet中共享,比方咱们可使用单例模式来处理共享数据。 

HttpSession:(线程是不安全的) 

HttpSession对象在用户会话期间存在,只能在处理属于同一个Session的请求的线程中被访问,所以Session对象的属性访问理论上是线程安全的。 

当用户打开多个同属于一个进程的浏览器窗口,在这些窗口的访问属于同一个Session,会出现屡次请求,须要多个工做线程来处理请求,可能形成同时多线程读写属性。这时咱们须要对属性的读写进行同步处理:使用同步块Synchronized和使用读/写器来解决。 

ServletRequest:(线程是安全的) 

对于每个请求,由一个工做线程来执行,都会建立有一个新的ServletRequest对象,因此ServletRequest对象只能在一个线程中被访问。ServletRequest是线程安全的。注意:ServletRequest对象在service方法的范围内是有效的,不要试图在service方法结束后仍然保存请求对象的引用。 


3,使用同步的集合类: 

使用Vector代替ArrayList,使用Hashtable代替HashMap。 


4,不要在Servlet中建立本身的线程来完成某个功能。 

Servlet自己就是多线程的,在Servlet中再建立线程,将致使执行状况复杂化,出现多线程安全问题。 


5,在多个servlet中对外部对象(比方文件)进行修改操做必定要加锁,作到互斥的访问。 


6,javax.servlet.SingleThreadModel接口是一个标识接口,若是一个Servlet实现了这个接口,那Servlet容器将保证在一个时刻仅有一个线程能够在给定的servlet实例的service方法中执行。将其余全部请求进行排队。 



PS:

Servlet并不是只是单例的. 当container开始启动,或是客户端发出请求服务时,Container会按照容器的配置负责加载和实例化一个Servlet(也能够配置为多个,不过通常不这么干).不过通常来讲一个servlet只会有一个实例。

1) Struts2的Action是原型,非单实例的;会对每个请求,产生一个Action的实例来处理。 

2) Struts1的Action,Spring的Ioc容器管理的bean 默认是单实例的. 


Struts1 Action是单实例的,spring mvc的controller也是如此。所以开发时要求必须是线程安全的,由于仅有Action的一个实例来处理全部的请求。单例策略限制了Struts1 Action能做的事,而且要在开发时特别当心。Action资源必须是线程安全的或同步的。 

Spring的Ioc容器管理的bean 默认是单实例的。

Struts2 Action对象为每个请求产生一个实例,所以没有线程安全问题。(实际上,servlet容器给每一个请求产生许多可丢弃的对象,而且不会致使性能和垃圾回收问题)。

当Spring管理Struts2的Action时,bean默认是单实例的,能够经过配置参数将其设置为原型。(scope="prototype )





Servlet的生命周期:


1.      Servlet在web服务器启动时被加载并实例化,容器运行其init方法初始化,请求到达时运行其service方法;


2.      service运行请求对应的doXXX(doGet,doPost)方法;


3.      服务器销毁实例,运行其destory方法;


Servlet的生命周期由Servlet容器管理;


(三个概念的理解:


Servlet容器<Web容器<应用服务器?


Servlet容器的主要任务就是管理Servlet的生命周期;


Web容器也称之为web服务器,主要任务就是管理和部署web应用的;


应用服务器的功能很是强大,不只能够管理和部署web应用,也能够部署EJB应用,实现容器管理的事务等等。。。



Web服务器就是跟基于HTTP的请求打交道,而EJB容器更可能是跟数据库,事务管理等服务接口交互,因此应用服务器的功能是不少的。


常见的web服务器就是Tomcat,但Tomcat一样也是Servlet服务器;


常见的应用服务器有WebLogic,WebSphere,但都是收费的;


没有Servlet容器,能够用Web容器直接访问静态Html页面,好比安装了apache等;若是须要显示Jsp/Servlet,就须要安装一个Servlet容器;可是光有servlet容器也是不够的,它须要被解析为html显示,因此仍须要一个web容器;因此,咱们常把web容器和Servlet容器视为一体,由于他们两个容器都有对方的功能实现了,都没有独立的存在了,好比tomcat!




Servlet是如何处理多个请求同时访问呢?


Servlet容器默认是采用单实例多线程的方式处理多个请求的:


1.      当web服务器启动的时候(或客户端发送请求到服务器时),Servlet就被加载并实例化(只存在一个Servlet实例);


2.      容器初始化Servlet。主要就是读取配置文件(例如tomcat,能够经过servlet.xml的<Connector>设置线程池中线程数目,初始化线程池;经过web.xml,初始化每一个参数值等等);


3.      当请求到达时,Servlet容器经过调度线程(Dispatchaer Thread)调度它管理下的线程池中等待执行的线程(Worker Thread)给请求者;


4.      线程执行Servlet的service方法;


5.      请求结束,放回线程池,等到被调用;


从上面能够看出:


第一:Servlet单实例,减小了产生servlet的开销;


第二:经过线程池来响应多个请求,提升了请求的响应时间;


第三:Servlet容器并不关心到达的Servlet请求访问的是不是同一个Servlet仍是另外一个Servlet,直接分配给它一个新的线程;若是是同一个Servlet的多个请求,那么Servlet的service方法将在多线程中并发的执行;


第四:每个请求由ServletRequest对象来接受请求,由ServletResponse对象来响应该请求;



问题出现了:


同一个Servlet的的多个请求到来时,若是该Servlet中存在成员变量,可能发生多线程同时访问该资源时,都来操做它,形成数据的不一致,所以产生线程安全问题。


解决:


1.      实现SingleThreadModel接口


若是一个Servlet被这个接口指定,那么在这个Servlet中的service方法将不会有两个线程被同时执行,固然也就不存在线程安全的问题;


2.      同步对共享数据的操做


使用synchronized关键字能保证一次只有一个线程能够访问被保护的区段,Servlet能够经过同步块操做来保证线程的安全。


ServletRequest对象是线程安全的,可是ServletContext和HttpSession不是线程安全的;


要使用同步的集合类:Vector代替ArrayList,HsahTable代替HashMap;


3.      避免使用实例变量(成员变量)


线程安全问题是由实例变量形成的,只要在Servlet里面的任何方法里面都不使用实例变量,那么该Servlet就是线程安全的。(全部建议不要在servlet中定义成员变量,尽可能用局部变量代替)



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


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