目录
一、什么是servlet
servlet是在服务器端运行的Java程序,可以接收客户端请求,可以动态生成HTML内容对客户端进行响应,它默认继承HttpServlet类,所以通常可以理解为继承了HttpServlet类的java类就是一个servlet。
二、servlet的生命周期
- 对象的生命周期,就是对象从出生到死亡的过程。即:出生->或者->死亡。也是对象从创建到销毁的过程。
- 出生:请求第一次到达servlet时,对象就创建出来,并且初始化成功,只创建一次,将对象放到内存中。
- 活着:服务器提供服务的整个过程中,该对象一直存在,每次都是执行service()方法。
- 死亡:当服务器停止时,或者服务器宕机时,对象死亡。
- 结论:servlet对象只会创建一次,销毁一次,所以servlet对象只有一个实例。
生命周期在我们的servlet中体现在以下方法中:init() -> service() -> destroy()
1.init()方法由 Servlet 容器调用,用于完成Servlet对象在处理客户请求 前的初始化工作。
2.service()方法是servlet接口中的方法,客户端发送请求后会请求接口中的service()方法,而service方法会根据请求方式的不同转发到不同的doXXX方法中,而我们使用的servlet类会重写父类的doXXX方法。
3.destory()方法由 Servlet 容器调用,释放Servlet对象所使用的资源。
下面我们使用代码进行演示:
首先,我们在servlet中的每个方法里分别打印下面的内容,然后启动服务器进行测试:
启动服务器后,我们发现init()方法和doGet()方法执行了,说明servlet对象创建并初始化了,此时我们首次访问servlet便是一个请求,所以也调用了一次doGet()方法进行服务
随后,我们在浏览器上在进行两次刷新,我们发现doGet()方法又执行了两次,即每一次请求都会调用service()方法服务一次。
最后,我们关闭服务器,servlet对象也随之销毁,此时一个完整的servlet生命周期就结束了。
三、servlet线程安全问题
1.线程安全测试
由于servlet采用的是单实例,也就是整个应用中只有一个实例对象,所以我们需要分析一下,这个唯一的实例对象中的类成员是否线程安全。
下面我们来看一个小案例:
首先我们定义一个用户名的成员变量,然后获取客户端发送过来的用户信息,最后再响应给客户端。
此时,我们在浏览器中传入aaa用户请求服务器,这个时候浏览器上理所当然的显示出了aaa用户的信息。
我们在获取用户名和响应用户信息之间加一个线程睡眠,然后让两个不同的浏览器同时去访问服务器。
这个时候我们使用火狐浏览器去请求aaa,使用谷歌浏览器请求bbb,会发现谷歌浏览器请求的bbb依然显示的是bbb用户,而火狐浏览器请求的aaa也显示了bbb用户。
结论:
一个浏览器代表了一个线程,多个浏览器代表多个线程。按理说我们期望应该是每个浏览器查看的都是自己的用户名,而现在我们看到的确是混乱的用户数据。因此我们可以认为servlet是线程不安全的!
2.servlet线程安全的解决方案
(1)将成员变量变成局部变量
我们将用户名的成员变量改成局部变量,再次测试上面的小案例,发现没有出现上面的线程安全问题,每个用户也成功访问到了自己的用户信息。
(2)加锁
我们给下面发生线程安全的代码加上一个锁,依然用的是成员变量,再次测试发现依然没有发生线程安全问题,每个用户依然可以访问到自己的用户信息。
解决:
定义类成员要谨慎,如果是公用的,并且只会在初始化时赋值,其他时间都是获取的话,那么是没问题的。如果不是公用的,或者每次使用都有可能对其进行赋值/改变,那就要考虑线程安全的问题了,可以将其定义到doGet或doPost方法内或者使用同步功能即可。
总结
通过servlet的生命周期我们可以看到servlet是单实例的,整个应用中只有一个servlet实例对象,所以他是线程不安全的,可以通过改变变量的作用范围或者加锁来解决线程安全问题。