Servlet生命周期与线程安全

时间:2022-05-22 11:10:23

上一篇介绍了Servlet初始化,以及如何处理HTTP请求,实际上在这两个过程中,都伴随着Servlet的生命周期,都是Servlet生命周期的一部分。同时,由于Tomcat容器默认是采用单实例多线程的方式处理多个请求,这一特性就导致了线程安全问题的存在。因此,本篇主要讲述Servlet生命周期与线程安全问题。

1、Servlet生命周期

Servlet是运行在容器当中的,所以其生命周期也由容器控制,最常用的容器就是Tomcat,笔者经历过的所有项目也都是以Tomcat作为Servlet的容器。经过前面几篇介绍,相信大家对Servlet的生命周期有了一定的了解。Servlet的生命周期其实是通过javax.servlet.Servlet接口中的init()、service()、destroy()等方法表示的,主要有四个阶段组成:加载并实例化、初始化(init())、处理请求(service())、销毁(destroy())。下面分别介绍这四个阶段。

加载并实例化

        Tomcat容器负责Servlet的加载并实例化,其实例化分两种情况:当web.xml文件里配置了<load-on-startup>标签并且里面的数字>=0时,容器启动时即加载Servlet类并创建类的实例;如果未配置<load-on-startup>标签或数字<0时,容器启动时不会加载Servlet类,当然也就不会创建类的实例。这时,当用户首次访问Servlet类时会加载并实例化。无论采用哪种方式实例化,都只会创建一个类的实例,无论多少用户访问Servlet,都共用这一个实例。

初始化(init())

在Servlet类实例化之后,容器将调用init()方法,传递ServletConfig接口的对象,进行初始化。在init()方法中,可以通过getServletConfig()方法获取ServletConfig对象,然后通过此对象的getInitParameter()等方法获取web.xml文件中<init-param>标签里面的配置信息,并对配置信息进行解析,或者执行任何其他一次性活动。在Servlet的整个生命周期中,init()方法只会被执行一次。

处理请求(service())

        在Servlet初始化完成之后,容器就准备接收并处理客户的请求了。处理请求时,容器会调用Servlet的service(HttpServletRequest req, HttpServletResponse resp)方法,这个方法会判断用户发送的请求类型,是“POST”请求还是“GET”请求或是其他请求,然后根据请求类型执行相应的doPost()方法、doGet()方法或其他方法。Tomcat容器会将用户请求的数据封装到HttpServletRequest对象中,服务器处理完用户请求之后,将结果信息返回到HttpServletResponse对象中,最终这两个对象作为参数传递到doPost()、doGet()或其他方法中,将结果信息返回到页面显示。当多个客户的请求到来时,服务器会创建多个线程,每个客户请求对应一个线程,每个请求的service()方法都能运行在自己独立的线程中。

销毁(destroy())

        当Tomcat容器关闭时或由于其他原因导致Servlet需要关闭或卸载时,容器会调用该对象的destroy()方法,以便让Servlet对象可以释放它所使用的资源,该方法同样只会执行一次。在容器调用destroy()方法前,如果还有其他的线程正在service()方法中执行,容器会等待这些线程执行完毕或者等待服务器设定的超时值到达。一旦Servlet对象的destroy()方法被调用,容器会释放这个Servlet对象,在随后的时间内,该对象会被java的垃圾收集器所回收。这四个阶段共同组成了Servlet的生命周期。

2、Servlet线程安全

通过上面的Servlet生命周期可以看出,在Tomcat容器加载并实例化Servlet之后,会创建一个实例,并且这个实例是唯一的,无论多少用户访问Servlet,都共用这一个实例。而每次用户访问Servlet时,服务器都会为每个用户创建一个独立的线程,每个线程都有它自己的堆栈空间。所以说是单实例多线程,这种默认以多线程方式执行的设计可大大降低对系统的资源需求,提高系统的并发量及响应时间,但也同时引发了Servlet的线程安全问题。

对于Servlet中的局部变量,多线程下每个线程对局部变量都会有自己的一份copy,存在自己的堆栈空间中,这样对局部变量的修改只会影响到自己的copy而不会对别的线程产生影响,所以这是线程安全的;对于Servlet中的实例(全局)变量,多线程下所有线程共享实例变量,这一共享就可能导致多个线程之间互相影响,从而引发线程的不安全。

知道了引发线程不安全问题的原因,那么该如何预防这一情况发生呢?

不使用实例变量

既然实例变量能引发线程安全问题,那么只要在Servlet类的任何方法里面都不使用实例变量,该Servlet就是线程安全的。事实上,线程安全问题大部分是由实例变量造成的,在Servlet中避免使用实例变量是保证Servlet线程安全的最佳选择。

使用synchronized

        synchronized关键字能保证一次只有一个线程可以访问被保护的区段,所以理论上可以通过同步块操作来保证Servlet的线程安全。但因为其“一次只有一个线程可以访问”的特性,导致当大量用户访问同一资源时,只能排队访问,大量用户处于阻塞状态,这就大大降低了其用户的吞吐量,从而使系统的效率和性能大大降低,不推荐使用此方法。

其他方式

Java的有些集合类也会引发线程安全问题,应避免使用。比如用Vector代替ArrayList,用Hashtable代替HashMap等。另外,不要在Servlet中创建其他线程来完成某个功能,因为Servlet本身就是多线程的,再在Servlet中创建线程,更容易引发线程安全问题。

转载请注明出处 http://www.cnblogs.com/Y-oung/p/8433426.html

工作、学习、交流或有任何疑问,请联系邮箱:yy1340128046@163.com  微信:yy1340128046