1. 程式人生 > >聊聊Servlet、Struts1、Struts2以及SpringMvc中的執行緒安全

聊聊Servlet、Struts1、Struts2以及SpringMvc中的執行緒安全

前言

很多初學者,甚至是工作1-3年的小夥伴們都可能弄不明白?servlet Struts1 Struts2 springmvc 哪些是單例,哪些是多例,哪些是執行緒安全?

在談這個話題之前,我們先了解一下Java中相關的變數型別以及記憶體模型JMM。

變數型別

  • 類變數:獨立於方法之外的變數,用 static 修飾。
  • 區域性變數:類的方法中的變數。
  • 例項變數(全域性變數):獨立於方法之外的變數,不過沒有 static 修飾。

JAVA的區域性變數

  • 區域性變數宣告在方法、構造方法或者語句塊中;
  • 區域性變數在方法、構造方法、或者語句塊被執行的時候建立,當它們執行完成後,變數將會被銷燬;
  • 訪問修飾符不能用於區域性變數;
  • 區域性變數只在宣告它的方法、構造方法或者語句塊中可見;
  • 區域性變數是在棧上分配的。
  • 區域性變數沒有預設值,所以區域性變數被聲明後,必須經過初始化,才可以使用。

JAVA的例項變數

  • 例項變數宣告在一個類中,但在方法、構造方法和語句塊之外;
  • 當一個物件被例項化之後,每個例項變數的值就跟著確定;
  • 例項變數在物件建立的時候建立,在物件被銷燬的時候銷燬;
  • 例項變數的值應該至少被一個方法、構造方法或者語句塊引用,使得外部能夠通過這些方式獲取例項變數資訊;
  • 例項變數可以宣告在使用前或者使用後;
  • 訪問修飾符可以修飾例項變數;
  • 例項變數對於類中的方法、構造方法或者語句塊是可見的。一般情況下應該把例項變數設為私有。通過使用訪問修飾符可以使例項變數對子類可見;
  • 例項變數具有預設值。數值型變數的預設值是0,布林型變數的預設值是false,引用型別變數的預設值是null。變數的值可以在宣告時指定,也可以在構造方法中指定;例項變數可以直接通過變數名訪問。但在靜態方法以及其他類中,就應該使用完全限定名:ObejectReference.VariableName。

JAVA的類變數(靜態變數)

  • 類變數也稱為靜態變數,在類中以static關鍵字宣告,但必須在方法構造方法和語句塊之外。
  • 無論一個類建立了多少個物件,類只擁有類變數的一份拷貝。
  • 靜態變數除了被宣告為常量外很少使用。常量是指宣告為public/private,final和static型別的變數。常量初始化後不可改變。
  • 靜態變數儲存在靜態儲存區。經常被宣告為常量,很少單獨使用static宣告變數。
  • 靜態變數在程式開始時建立,在程式結束時銷燬。
  • 與例項變數具有相似的可見性。但為了對類的使用者可見,大多數靜態變數宣告為public型別。
  • 預設值和例項變數相似。數值型變數預設值是0,布林型預設值是false,引用型別預設值是null。變數的值可以在宣告的時候指定,也可以在構造方法中指定。此外,靜態變數還可以在靜態語句塊中初始化。
  • 靜態變數可以通過:ClassName.VariableName的方式訪問。
  • 類變數被宣告為public static final型別時,類變數名稱一般建議使用大寫字母。如果靜態變數不是public和final型別,其命名方式與例項變數以及區域性變數的命名方式一致。

Java的記憶體模型JMM

Java的記憶體模型JMM(Java Memory Model)JMM主要是為了規定了執行緒和記憶體之間的一些關係。根據JMM的設計,系統存在一個主記憶體(Main Memory),Java中所有例項變數都儲存在主存中,對於所有執行緒都是共享的。每條執行緒都有自己的工作記憶體(Working Memory),工作記憶體由快取和堆疊兩部分組成,快取中儲存的是主存中變數的拷貝,快取可能並不總和主存同步,也就是快取中變數的修改可能沒有立刻寫到主存中;堆疊中儲存的是執行緒的區域性變數,執行緒之間無法相互直接訪問堆疊中的變數。根據JMM,我們可以將論文中所討論的Servlet例項的記憶體模型抽象為下圖所示的模型。

執行緒安全

Servlet

Servlet/JSP技術和ASP、PHP等相比,由於其多執行緒執行而具有很高的執行效率。由於Servlet/JSP預設是以多執行緒模式執行的,所以,在編寫程式碼時需要非常細緻地考慮多執行緒的安全性問題。然而,很多人編寫Servlet/JSP程式時並沒有注意到多執行緒安全性的問題,這往往造成編寫的程式在少量使用者訪問時沒有任何問題,而在併發使用者上升到一定值時,就會經常出現一些莫明其妙的問題。

Servlet的多執行緒機制

Servlet體系結構是建立在Java多執行緒機制之上的,它的生命週期是由Web容器負責的。當客戶端第一次請求某個Servlet 時,Servlet容器將會根據web.xml配置檔案例項化這個Servlet類。當有新的客戶端請求該Servlet時,一般不會再例項化該 Servlet類,也就是有多個執行緒在使用這個例項。Servlet容器會自動使用執行緒池等技術來支援系統的執行,如下圖所示。

這樣,當兩個或多個執行緒同時訪問同一個Servlet時,可能會發生多個執行緒同時訪問同一資源的情況,資料可能會變得不一致。所以在用Servlet構建的Web應用時如果不注意執行緒安全的問題,會使所寫的Servlet程式有難以發現的錯誤。

Servlet的執行緒安全問題

Servlet的執行緒安全問題主要是由於例項變數使用不當而引起的,這裡以一個現實的例子來說明。

/** * 模擬使用者AB在同時執行不同的動作 * 先執行 http://localhost:8080/concurrent?username=A&action=play * 稍後執行 http://localhost:8080/concurrent?username=B&action=eat */
public class Concurrent extends HttpServlet {
    private static final long serialVersionUID = 1L;
    private String action = "";//動作  
    publicvoiddoGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        try {  
            String username = request.getParameter("username");  
            action = request.getParameter("username");  
            Thread.sleep(5000); //為了突出併發問題,在這設定一個延時  
            //如果不出意外,應該使用者AB都在吃飯
            System.out.println("使用者:"+username+"在"+action);
        } catch (Exception e) {  
        }  
    }
}

Struts1

首先,明確一點Sturts1 action是單例模式,執行緒是不安全的。Struts1使用的ActionServlet是單例的,既然是單例,當使用例項變數的時候就會有執行緒安全的問題。所有一般在開發中試禁止使用例項變數的。

Struts2

struts2使用的是actionContext,都是使用裡面的例項變數,讓struts2自動匹配成物件的。每次處理一個請求,struts2就會例項化一個物件,這樣就不會有執行緒安全的問題了。

需要注意的是,如果struts2+spring來管理注入的時候,不要把Action設定成單例,否則會出問題的。當然現在很少有專案使用struts2了。

SpringMVC

SpringMVC的controller預設是單例模式的,所以也會有多執行緒併發的問題。

總結

  • servlet Struts1 SpringMvc 是執行緒不安全的,當然如果你不使用例項變數也就不存線上程安全的問題了。

  • Struts2 是執行緒安全的,當然前提情況是,Action 不交給 spring管理,並且不設定為單例。

  • SpringMvc 的 Bean 可以設定成多例變成執行緒安全,但是一定程度上回影響系統性能。