Spring bean Scope作用域及線程安全問題場景分析

Scope作用域

在 Spring IoC 容器中具有以下幾種作用域:

  • singleton:單例模式,在整個Spring IoC容器中,使用singleton定義的Bean將只有一個實例,適用於無狀態bean;
  • prototype:原型模式,每次通過容器的getBean方法獲取prototype定義的Bean時,都將產生一個新的Bean實例,適用於有狀態的Bean;
  • request:對於每次HTTP請求,使用request定義的Bean都將產生一個新實例,即每次HTTP請求將會產生不同的Bean實例。只有在Web應用中使用Spring時,該作用域纔有效;
  • session:對於每次HTTP Session,使用session定義的Bean豆漿產生一個新實例。同樣只有在Web應用中使用Spring時,該作用域纔有效;
  • globalsession:每個全局的HTTP Session,使用session定義的Bean都將產生一個新實例。典型情況下,僅在使用portlet context的時候有效。同樣只有在Web應用中使用Spring時,該作用域纔有效。

@scope默認是單例模式(singleton),如果需要設置的話@scope("prototype")
或xml配置如下:

 

<bean id="service1" class="com.test.TestServiceImpl1" scope="singleton" />  
<bean id="service2" class="com.test.TestServiceImpl2" scope="prototype" />  

無狀態會話bean

bean一旦實例化就被加進會話池中,各個用戶都可以共用。即使用戶已經消亡,bean 的生命期也不一定結束,它可能依然存在於會話池中,供其他用戶調用。
由於沒有特定的用戶,那麼也就不能保持某一用戶的狀態,所以叫無狀態bean。但無狀態會話bean 並非沒有狀態,如果它有自己的屬性(變量),那麼這些變量就會受到所有調用它的用戶的影響。

有狀態會話bean

每個用戶有自己特有的一個實例,在用戶的生存期內,bean保持了用戶的信息,即“有狀態”;一旦用戶滅亡(調用結束或實例結束),bean的生命期也告結束。即每個用戶最初都會得到一個初始的bean。

有狀態bean,如果配置爲singleton,會出現線程安全問題

示例:

 

package com.test;    
public class TestServiceImpl implements TestService{  
    private User user;
    public void test1(User u) throws Exception {  
        this.user = u;                          //1  
        test2();  
    }  
    
    public void test2() throws Exception {  
        System.out.println(user.getId());       //2 
    }     
}

如果該Bean配置爲singleton,在併發訪問下會出現問題
假設有2個用戶user1,user2訪問,都調用到了該Bean。
1.當user1 調用到程序中的1步驟的時候,該Bean的私有變量user被付值爲user1;
2.理想的狀況,當user1走到2步驟的時候,私有變量user應該爲user1;
3.但如果在user1調用到2步驟之前,user2開始運行到了1步驟了,由於單態的資源共享,則私有變量user被修改爲user2;
4.這種情況下,user1的步驟2用到的user.getId()實際用到是user2的對象。
實際應該是這個例子不應該用實例變量,這樣就使得這個Bean由無狀態變成了有狀態Bean。

常用web架構,線程安全問題場景分析

1.SSH架構系統

對於SSH架構的系統,很少關心這方面,因爲我們用到的一般都是singleton. Bean的注入由Spring管理。

Struts2中的Action因爲會有User這樣的實例對象,是有狀態信息的,在多線程環境下是不安全的,所以Struts2默認的實現是Prototype模式。也就是每個請求都新生成一個Action實例,所以不存在線程安全問題。需要注意的是,如果由Spring管理action的生命週期, scope要配成prototype作用域。

Struts1是基於單例模式實現,也就是隻有一個Action實例供多線程使用。默認的模式是前臺頁面數據通過actionForm傳入,在action中的excute方法接收,這樣action是無狀態的,所以一般情況下Strunts1是線程安全的。如果Action中用了實例變量,那麼就變成有狀態了,同樣是非線程安全的。像下面這樣就是線程不安全的。

 

/** 
 * 非線程安全的Struts1示例  
 *  
 */  
public class UserAction extends Action {  
    // 因爲Struts1是單例實現,有狀態情況下,對象引用是非線程安全的  
    private User user;  
    public void execute() {  
        // do something...  
    }  
    public User getUser() {  
        return user;  
    }  
    public void setUser(User user) {  
        this.user = user;  
    }  
}  

2.Servlet

Servlet體系結構是建立在Java多線程機制之上的,它的生命週期是由Web 容器負責的。
一個Servlet類在Application中只有一個實例存在,有多個線程在使用這個實例。這是單例模式的應用。
無狀態的單例是線程安全的,但我們如果在Servlet裏用了實例變量(私有變量),那麼就變成有狀態了,是非線程安全的。
如下面的用法就是不安全的,因爲user是有狀態信息的。

 

public class UserServlet HttpServlet{     
    private User user;   
    public void doGet (HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException{  
        //do something...  
    }  
} 

Spring容器中的Bean是否線程安全,容器本身並沒有提供Bean的線程安全策略,因此可以說Spring容器中的Bean本身不具備線程安全的特性,但是具體還是要結合具體scope的Bean去研究。

Spring 的 bean 作用域(scope)類型

1、singleton:單例,默認作用域。

2、prototype:原型,每次創建一個新對象。

3、request:請求,每次Http請求創建一個新對象,適用於WebApplicationContext環境下。

4、session:會話,同一個會話共享一個實例,不同會話使用不用的實例。

5、global-session:全局會話,所有會話共享一個實例。

線程安全這個問題,要從單例與原型Bean分別進行說明。

原型Bean

對於原型Bean,每次創建一個新對象,也就是線程之間並不存在Bean共享,自然是不會有線程安全的問題。

單例Bean

對於單例Bean,所有線程都共享一個單例實例Bean,因此是存在資源的競爭。

如果單例Bean,是一個無狀態Bean,也就是線程中的操作不會對Bean的成員執行查詢以外的操作,那麼這個單例Bean是線程安全的。比如Spring mvc 的 Controller、Service、Dao等,這些Bean大多是無狀態的,只關注於方法本身。

對於有狀態的bean,Spring官方提供的bean,一般提供了通過ThreadLocal去解決線程安全的方法,比如RequestContextHolder、TransactionSynchronizationManager、LocaleContextHolder等。

使用ThreadLocal的好處

使得多線程場景下,多個線程對這個單例Bean的成員變量並不存在資源的競爭,因爲ThreadLocal爲每個線程保存線程私有的數據。這是一種以空間換時間的方式。

當然也可以通過加鎖的方法來解決線程安全,這種以時間換空間的場景在高併發場景下顯然是不實際的。

總結

  • singleton會造成資源混亂問題,而如果是prototype的話,就不會出現資源共享的問題。(即不會出現線程安全的問題)
  • 應該儘量使用無狀態Bean.如果在程序中出現私有變量(該bean會變爲有狀態的,一旦在其他線程中發生改變,就會產生線程不安全),解決方案就是儘量替換爲方法中的參數。對於每個訪問私有變量的方法增加變量傳入(參數傳入)或者通過ThreadLocal來獲取。
  • 如果用有狀態的bean,就要用prototype模式,每次在注入的時候就重新創建一個bean,在多線程中互不影響。
  • 如Service層、Dao層用默認singleton就行,雖然Service類也有dao這樣的屬性,但dao這些類都是沒有狀態信息的,也就是相當於不變(immutable)類,所以不影響。
  • Stateless無狀態用單例Singleton模式,Stateful有狀態就用原型Prototype模式。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章