前言
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 作用域。
最后兩條很重要喔!
