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對象,完成成員變量賦值。