Servlet/JSP、Struts1、Struts2以及SpringMVC的线程安全性
一、Servlet/JSP
当客户端第一次请求Servlet时,Web容器会根据web.xml中的配置文件创建一个Servlet实例,而后调用init()方法,仅一次(注意);之后每一次请求都会执行Servlet实例中的service()方法;最后在容器销毁时,调用destroy()方法。
我们大可以通过以上内容推测Servlet处理请求的方式:单实例、多线程。我们来看看下图,了解一下是什么意思:
当客户端第一次发出对这个Servlet的请求时,Tomcat会新建这个Servlet的实例,此后就不会再去新建Servlet实例,这也能够解释,为什么init()方法只执行一次了。
而当有多次的客户端请求时,Tomcat会分配一个新的线程去处理这次请求,操作同一个Servlet实例。
好了线程安全问题由此引发,我们编写如下一段代码:
protected void doGet(javax.servlet.http.HttpServletRequest request, javax.servlet.http.HttpServletResponse response) throws javax.servlet.ServletException, IOException {
String username = request.getParameter("username");
action = request.getParameter("action");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(username + "正在" +action);
}
代码比较简单,为一个doGet请求方法,获取username和action两个参数,其中action为Servlet的成员变量,username为局部变量,为了更加容易地去制造并发问题,我们加了一个Thread.sleep(),最后打印结果。我们用以下两组URL,先后输入在浏览器上,测试以上代码:
得到如下的结果:
很显然,也很完美的一个非线程安全事故。具体原因在于:局部变量是在每一个线程的栈的栈帧上分配的,而对象的内存是在堆上分配,在A“eat”的时候,B擅自将action改为了“fly”,导致A在休眠五秒后发现自己不是在吃东西,而是在飞。
所以,如果在使用Servlet构建web项目时,如果不考虑线程安全,很容易发生隐患。
二、Struts1
三、Struts2
Struts2是一个很老旧的MVC框架了,网上已经说基本上项目不会再去使用Struts2了,但是于2018年5月末,鄙人公司依旧在使用着Struts2。。。Struts2使用的actionContext,在使用中传参大多依赖于成员变量,自动给成员变量赋值,所以为了避免线程安全问题,Struts2是多例的。
但是当与Spring整合时需要注意,必须避免将其Action设置为单例的,否则会发生线程安全问题。
四、SpringMVC
作为现在主流的MVC框架,SpringMVC还是拥有着强大的生命力的,他的Controller类是和Spring整合的,所以如果不去修改Bean的单例模式,Controller类就是单例的。但是他的传参不依赖于成员变量,所以,当没有在Controller类中使用成员变量,就不会有线程安全问题。
五、说说SpringMVC和Struts2的区别和优缺点
- 实现机制
SpringMVC使用的是Servlet实现中央控制器,Struts2使用的是Filter实现的请求拦截。就Servlet和Filter的区别来说,Servlet是在第一次请求时实例,Filter是在容器加载时便实例化。 - 拦截机制
Struts2的拦截是类级别的,对每一个Request的传参,通过调用成员变量的getter和setter方法赋值,所以对每一个Action类都生成一个实例。
SpringMVC的拦截是方法级别的,一个URL对应一个方法,传入参数直接注入给方法的局部变量,包括它的request和response对象也是方法中的局部变量,所以也不存在线程安全问题。 - 性能方面
由于单例多例的原因,SpringMVC性能肯定是优于Struts2的,因为Struts2每次请求都需要生成一个Action对象,完成成员变量赋值。