Spring框架中在併發訪問時的線程安全性
在今天整理往前所學的知識中,看到了關於HashMap是線程不安全的,在之前的實際項目開發中自己在寫一個消息通知的接口功大家調用的時候,因爲一個消息通知的模板中需要替換的參數長度和數量不定,並且相同的參數可能對應不同的內容,爲了方便大家調用我的這個方法,我使用了HashMap作爲參數,並把它寫在了Service層中一個方法內 。使用的框架是Spring+SpringMvc+SpringBoot+Mybatis+Theamleaf,當時在使用的時候知道如果前端發來多個請求的話Spring框架是將每一個請求都開啓一個線程,但在Spring框架中Controller層和Service層和Dao層都用了加@Autowired註解的方式將對應的類注入到Spring中,再由Spring框架通過反射代理的方式生產一個單例,那麼這樣的後果是Spring框架在多線程中是線程不安全的,因爲多個線程共用一個實例,但是爲什麼我在一個方法內寫了線程並不安全的HashMap卻沒有錯誤呢?
一、SpringMvc下的controller和Service和Dao的創建模式
SpringMvc在默認配置下創建controller和Service和Dao是singleton的(非線程安全的),這也是和Struts2的區別,Struts2中會爲每一個請求都創建對應的實例,所以它是線程安全的。
Spring這樣做的好處:一是我們不用每次創建Controller,二是減少對象的創建和垃圾回收的頻率,但是在多線程調用的時候因爲都使用的是同一個instance,所以這個instance是線程不安全的,出現數據錯誤或者其他意想不到的結果。
二、怎麼更改Spring默認創建的模式
最淺顯的解決辦法就是將多態bean的作用域由“singleton”變更爲“prototype”
<bean id="testManager" class="com.sw.TestManagerImpl" scope="singleton" />
<bean id="testManager" class="com.sw.TestManagerImpl" scope="prototype" />
singleton表示該bean全局只有一個實例,Spring中bean的scope默認也是singleton.
prototype表示該bean在每次被注入的時候,都要重新創建一個實例,這種情況適用於有狀態的Bean.
三、爲什麼我的HashMap是安全的,不是說Spring框架是線程不安全的嘛?
我們首先來看一段線程不安全的HashMap代碼:
@RequestMapping("/user")
@Controller
Class UserController
{
@Resource
UserService userService;
@RequestMapping("/add")
public void testA(User user){
userService.add(user);
}
@RequestMapping("/get")
public void testA(int id){
userService.get(id);
}
}
@Service("userService")
Class UserService{
public static Map<Integer,User> usersCache = new HashMap<String,User>();
public void add(User user){
usersCache.put(user.getId(),user);
}
public void get(int id){
usersCache.get(id);
}
}
此段代碼,usersCache對象就是線程不安全的。因爲它是靜態的全局共享對象。如果有多個線程同時調用add方法,可能會發生用戶對象被覆蓋的情況,也就是id對應對象不一致,這是多線程編程中最常發生的事情。
所以,可以使用 Collections 工具同步Map。
static Map<Integer, Users> usersCache = Collections.synchronizedMap(new HashMap<Integer, Users>());
或者也可以使用ConCurrent併發包下的ConCurrentHashMap來解決這個問題。
那麼爲什麼我的HashMap也是在Service層寫的,但卻是線程安全的呢?
因爲我的HashMap並不是一個靜態的全局共享對象,只是一個方法裏面的,我們來看我的代碼:
這是我對應的Service層的方法,在這裏我調用了我封裝的一個發送消息的封裝SendMessageUtil類來將消息生成,
可以看得出這裏的HashMap並不是靜態全局變量,它是屬於一個方法的局部變量,方法的調用是屬於JVM中的虛擬機棧的,是線程私有的,所以不會產生線程不安全問題。
總而言之
在Spring框架中只要將某個屬性設置爲靜態全局變量就會有線程安全的問題,只要寫在某一個方法內就不會有線程安全問題。