首先我们必须要先了解servlet的生命周期:
服务器只创建每个servlet的单一实例,首次创建servlet时,它的init方法会被调用,因此,init是放置一次性设置代码的地方,之后,针对每个用户的请求都会创建一个线程,该线程调用前面创建的实例方法。多个并发请求一般会导致多个线程同时调用service(线程安全),service方法会依据接受到HTTP请求的类型,调用doXXX方法。最后如果服务器卸载某个servlet就会调用servlet的destroy方法。
servlet是否线程安全?
通常情况下,系统只生成servlet的单一实例之后,为每个用户请求建立新的线程。如果很多请求同时到来,那么多个线程可能会并发的访问同一个servlet对象。因此我们必须小心地同步对字段以及全局变量和其它共享数据的访问,因为多个线程可能同时对同一数据进行访问。(多个线程并不共享局部变量)。
解决方法:
原则上,可以让servlet实现singleThreadModel接口,阻止多线程访问
Public class Servlet1 extends HttpServlet implements SingleThreadModel{
……..
}
实现此接口。大多数情况下,系统将所有的请求排队,一次只将一个请求发送给单个servlet实例。但是,服务器也可以创建由多个实例组成的池,同一时间每个实例都能够请求处理,此时我们不需要担心队servlet常规字段(实例变量)的同时访问,但是必须同步对类变量或存储在servlet之外的共享数据的访问,
大部分情况下,SingleThreadModel并不是一个很好的选项。如果servlet被频繁访问,那么同步对servlet的访问对性能造成极大的损害(等待时间)。在servlet等待I/O,数据库访问等其它操作时,服务器不能处理同一servlet的挂起请求。
singleThreadModel的第二个问题来源于,Servlet规范允许服务器使用多个实例来处理请求来替代对单个实例的请求进行排队的方案。只要每个实例同一时间处理一个请求,实例共享的方式就满足规范的要求,但这不是一个好的方案:
当使用非静态实例变量存储共享数据,singleThreadModel会阻止并发的访问,但是每个servlet实例都拥有实例变量的单独副本,数据就不能共享,
使用静态实例变量存储共享数据:singleThreadModel的共享池也就没有任何优点,多个请求依旧会并发的访问静态数据。
对于高流量的servlet,使用明确的代码同步更好一些。
以一个简单的例子说明,给出3种方案
String id = “user id =” + userId;
Do something….
userId = userId + 1;
1:减少竞争
Sting id = “user id =” + userId++;这种方案减低了产生不正确答案的可能性,但没有完全消除,在很多情况下,减低错误出现的可能性是一场灾难,问题在测试中更难以发现
2:SingleThreadModel
将servlet做如下修改
public class UserId extends HttpServlet implements SingleThreadModel{
如果服务器是通过排队所有的请求来实现SingleThreadModel,那它能工作,但如果村子大量的并发访问,这种方式会导致性能上的巨大损失。
如果服务器通过产生servlet的多个实例来实现SingleThreadModel。这种方式会完全失败,因为每个实例都拥有自己的userId字段。
3:使用同步
Synchronized(this){
String id = “user id =” + userId;
Do something….
userId = userId + 1;
}
Struts 1 和 Struts 2 的线程安全