Spring Bean 中的線程安全

在使用Spring框架時,不少時候不知道或者忽視了多線程的問題。由於寫程序時,或作單元測試時,很難有機會碰到多線程的問題,由於沒有那麼容易模擬多線程測試的環境。但若是不去考慮潛在的漏洞,它就會變成程序的隱形殺手,在你不知道的時候爆發。並且,一般是程序交付使用時,在生產環境下觸發,會是很麻煩的事。java

 

那麼Spring Bean在大多數狀況下,對象實例(Object)和方法是否線程安全呢?spring

這就要看如何定義Bean的生命管理和範圍(Scope)了,就大多數狀況而言,若是不特別注意並採起方法,Spring Bean是非線程安全的。緣由是Spring Bean的產生是經過容器實現,而使用反向控制IoC時,注入的對象實例也是與其餘線程共享的。安全

 

例如session

public class PersonController{    
    private PersonService personService;    
    
    public void setFirstName(HttpServletRequest request){                    
        personService.setFirstName(request.getParameter("firstname"));    
    }    
    
    public String getFirstName(){             
        return   personService.getFirstName();    
    }
                 
}

當有兩個線程訪問PersonController時,一個先調用setFristName來設置firstname, 而另外一個線程調用getFirstName就會獲得第一個線程所設的值。這是由於,PersonController是缺省狀況下爲單一實例(Singleton),而personService也是一個單獨的實例被注入到personController裏。這種狀況下,多線程是不安全的,除非可以保證每一個方法(method)都不會改變所共享的對象的屬性或狀態。多線程

 

線程安全的方法併發

public class PersonController {   
 
    private PersonService personService
    
    public void setFirstName(HttpServletRequest request){        
      Person person = new Person();        
      person.setFirstName();        
      personService.savePerson(person);    
    }
 }

上面的例子是一種線程安全的寫法,例如,線程A訪問setFirstName方法,當它執行到person.setFirstName()後,系統決定掛起這個線程,轉向執行B,B作了一樣的事情並保存。而後,系統再執行被掛起的A線程,因爲A線程有本身的堆棧狀態,並且它自己沒有與其餘線程共享的對象實例或狀態。須要補充說一下,這裏假定personService.savePerson()方法是線程安全的,不然,還須要調查personService.savePerson()是否線程安全。框架

所以,在大多數狀況下,spring bean是非線程安全的,或者說,若是你不告訴它如何管理對象或方法的線程安全,那麼就會潛在線程安全問題。高併發

spring bean所以給出瞭如下的線程安全的可用聲明(annotation),你能夠根據實際狀況,分別定義你的類或方法。性能

 

單實例 singleton(缺省)

在整個Spring IoC容器裏,只有一個bean實例,全部線程共享該實例單元測試

 

原型實例prototype

每次請求都會建立並返回一個新的實例,全部線程都有單獨的實例使用,這種方式是比較安全的,但會消耗大量內存和計算資源。

定義bean的xml配置文件

<bean id="personService" class="com.test.PersonService" scope="prototype">    
<!-- inject dependencies here as required -->
</bean>
<bean id="personControoler" class="com.test.PersonController">    
    <lookup-method name="getPersonService" bean="personService"/>
</bean>


抽象類

public abstract class PersonController {    
    public void setFirstName(HttpServletRequest request){        
        Person person = new Person();        
        person.setFirstName();        
        personService.savePerson(person);    
     }   
     
     public abstract PersonService getPersonService();
 }


這裏,每次當類中的getPersonService()方法被調用時,就會獲得一個新personService實例。須要注意的是,PersonController變成了抽象類。

 

請求範圍實例request 

每當接受到一個HTTP請求時,就分配一個惟一實例,這個實例在整個請求週期都是惟一的。

 

會話範圍實例session

在每一個用戶會話週期內,分配一個實例,這個實例在整個會話週期都是惟一的,全部同一會話範圍的請求都會共享該實例。

 

全局話範圍實例globalsession

這與會話範圍實例大部分狀況是同樣的,只是在使用到portlet時,因爲每一個portlet都有本身的會話,若是一個頁面中有多個portlet而須要共享一個bean時,纔會用到。

 

Request範圍和Session範圍在高併發性的MVC環境下的使用

從上面得知,在缺省狀況下,被添加上聲明@Controller的類是單一實例的,所以,你須要特別留意類中的方法與變量的線程安全問題。

而當考慮到併發用戶的環境下,有些時候可使用Reqest範圍,例如,你須要爲每次用戶的請求分配一個新的控制器實例,這一般是面對一些處理邏輯很複雜的請求,須要佔用比較多的資源以及反應時間要求較高的狀況,好比,網上訂票或支付。

@Controller@Scope("request")
public class OnlinePaymentController {                 
    private OnlinePayment payment;          
    public void pay(Float amount){                    
        creditCheck(user);                    
        payment.pay(amount);                    
        ...                    
        //a lot of processes have to be done here          
    }
}



有些時候,能夠考慮使用Session範圍,針對每一個用戶,提供一個實例,應付用戶的屢次請求,例如,網上購物車。

@Controller
@Scope("session")
public class ShoppingCartController {       

          private ShoppingCart shoppingCart



          public void checkIn(ShoppingItem item, Integer number){                    
              shoppingCart.add(item, number);          
          }          

          public void checkOut(){                    
              shoppingCart.sum();          
          }
 }

 

實際生產環境中用的比較多的是單一實例與Session範圍,若是可以保證單一實例中的線程安全,那麼在性能上是首選的,畢竟,當網站用戶量不斷增長時,仍然可以用有限的實例來處理多個用戶請求是比較划算的。

相關文章
相關標籤/搜索