Spring中的單例Bean是線程安全的嗎


前言

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

Bean的作用域

   Spring 中,完整的 bean的作用域(scope)枚舉值如下:
   1、singleton:單例,默認作用域。
   2、prototype:多實例,每次創建一個新對象。
   3、request:請求,每次Http請求創建一個新對象,適用於WebApplicationContext環境下。
   4、session:會話,同一個會話共享一個實例,不同會話使用不用的實例。
   5、global-session:全局會話,所有會話共享一個實例。

其中,request、session和global-session這三個作用域只有在web開發中才會使用到。

Bean 狀態

   有狀態對象(Stateful Bean) :就是有實例變量的對象,可以保存數據,是非線程安全的。每個用戶有自己特有的一個實例,在用戶的生存期內,bean保持了用戶的信息,即“有狀態”;一旦用戶滅亡(調用結束或實例結束),bean的生命期也告結束。即每個用戶最初都會得到一個初始的bean。

   無狀態對象(Stateless Bean):就是沒有實例變量的對象,不能保存數據,是不變類,是線程安全的。bean一旦實例化就被加進會話池中,各個用戶都可以共用。即使用戶已經消亡,bean 的生命期也不一定結束,它可能依然存在於會話池中,供其他用戶調用。由於沒有特定的用戶,那么也就不能保持某一用戶的狀態,所以叫無狀態bean。但無狀態會話bean 並非沒有狀態,如果它有自己的屬性(變量),那么這些變量就會受到所有調用它的用戶的影響,這是在實際應用中必須注意的。

Bean的線程安全問題

   關於Bean的線程安全問題,要從單例與多實例Bean分別進行說明。多實例Bean每次創建一個新對象,也就是線程之間並不存在Bean共享,自然是不會有線程安全的問題。

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

   如果單例Bean是一個無狀態Bean,也就是線程中的操作不會對Bean的成員執行查詢以外的操作,那么這個單例Bean是線程安全的。

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

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

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

   Spring中的Controller ,Service,Dao是不是線程安全的?controller、service和dao層本身並不是線程安全的,如果只是調用里面的方法,而且多線程調用一個實例的方法,會在內存中復制變量,這是自己線程的工作內存,是安全的。

   下面以Controller為例,Service和Dao也是同樣的道理。

@RestController
@Scope(value = "prototype") // 加上@Scope注解,他有2個取值:單例-singleton 多實例-prototype
public class TestController {
    private int var = 0;
    private static int staticVar = 0;
 
    @GetMapping(value = "/test_var")
    public String test() {
        System.out.println("普通變量var:" + (++var)+ "---靜態變量staticVar:" + (++staticVar));
        return "普通變量var:" + var + "靜態變量staticVar:" + staticVar;
    }
}

   請求API三次,得到如下結果:

普通變量var:1---靜態變量staticVar:1
普通變量var:1---靜態變量staticVar:2
普通變量var:1---靜態變量staticVar:3

   雖然每次都是單獨創建一個Controller,但是扛不住它的類變量呀!故即便是加上@Scope注解也不一定能保證Controller 100%的線程安全。所以是否線程安全在於怎樣去定義變量以及Controller的配置。下面總結一下:

   1、在@Controller/@Service/@Repository等容器中,默認情況下,scope值是單例-singleton的,也是線程不安全的。

   2、盡量不要在@Controller/@Service/@Repository等容器中定義靜態變量,不論是單例(singleton)還是多實例(prototype),她都是線程不安全的。

   3、一定要定義變量的話,用ThreadLocal來封裝,這個是線程安全的。

   4、對有狀態的 bean 要使用 prototype

   5、作用域對無狀態的 bean 使用 singleton 作用域

  最后兩條很重要喔!

Reference


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM