1. 程式人生 > >springMVC一個Controller處理所有使用者請求的併發問題

springMVC一個Controller處理所有使用者請求的併發問題

有狀態和無狀態的物件基本概念: 
有狀態物件(Stateful Bean),就是有例項變數的物件 ,可以儲存資料,是非執行緒安全的。一般是prototype scope。
無狀態物件(Stateless Bean),就是沒有例項變數的物件,不能儲存資料,是不變類,是執行緒安全的。一般是singleton scope。

如Struts2中的Action,假如內部有例項變數User,當呼叫新增使用者方法時,user是用來儲存資料,那麼此action是有狀態物件。多個執行緒同時訪問此action時 會造成user變數的不一致。所以action的scope要設計成prototype,或者,User類放到threadLocal裡來保持多個執行緒不會造成User變數的亂串(此種場景沒必要放到threadLocal內)。

而Service內部一般只有dao例項變數 如userDao, 因為userDao是無狀態的物件(內部無例項變數且不能儲存資料),所以service也是無狀態的物件。

public   class  XxxAction{

   // 由於多執行緒環境下,user是引用物件,是非執行緒安全的  

  public  User user;

  ......

}

public   class  XxxService {  

  // 雖然有billDao屬性,但billDao是沒有狀態資訊的,是Stateless Bean.   

  BillDao billDao;  

  ......

}

對於那些會以多執行緒執行的單例類

區域性變數不會受多執行緒影響,
成員變數會受到多執行緒影響。

多個執行緒呼叫同一個物件的同一個方法: 
如果方法裡無區域性變數,那麼不受任何影響
如果方法裡有區域性變數,只有讀操作,不受影響;存在寫操作,考慮多執行緒影響值;

例如Web應用中的Servlet,每個方法中對區域性變數的操作都是線上程自己獨立的記憶體區域內完成的,所以是執行緒安全的。 
對於成員變數的操作,可以使用ThreadLocal來保證執行緒安全。 

springMVC中,一般Controller、service、DAO層的scope均是singleton;

每個請求都是單獨的執行緒,即使同時訪問同一個Controller物件,因為並沒有修改Controller物件,相當於針對Controller物件而言,只是讀操作,沒有寫操作,不需要做同步處理。

Service層、Dao層用預設singleton就行,雖然Service類也有dao這樣的屬性,但dao這些類都是沒有狀態資訊的,也就是 相當於不變(immutable)類,所以不影響。

Struts2中的Action因為會有User、BizEntity這樣的例項物件,是有狀態資訊 的,在多執行緒環境下是不安全的,所以Struts2預設的實現是Prototype模式。在Spring中,Struts2的Action中scope 要配成prototype作用域。

Spring併發訪問的執行緒安全性問題  

由於Spring MVC預設是Singleton的,所以會產生一個潛在的安全隱患。根本核心是instance變數保持狀態的問題。這意味著每個request過來,系統都會用原有的instance去處理,這樣導致了兩個結果:
一是我們不用每次建立Controller,
二是減少了物件建立和垃圾收集的時間;
由於只有一個Controller的instance,當多個執行緒同時呼叫它的時候,它裡面的instance變數就不是執行緒安全的了,會發生竄資料的問題。
當然大多數情況下,我們根本不需要考慮執行緒安全的問題,比如dao,service等,除非在bean中聲明瞭例項變數。因此,我們在使用spring mvc 的contrller時,應避免在controller中定義例項變數。 
如:

publicclassControllerextendsAbstractCommandController{
......
protectedModelAndView handle(HttpServletRequest request,HttpServletResponse response,
Object command,BindException errors)throwsException{
company =................;
}
protectedCompany company;
}

在這裡有宣告一個變數company,這裡就存在併發執行緒安全的問題。
如果控制器是使用單例形式,且controller中有一個私有的變數a,所有請求到同一個controller時,使用的a變數是共用的,即若是某個請求中修改了這個變數a,則,在別的請求中能夠讀到這個修改的內容。。

有幾種解決方法:
1、在控制器中不使用例項變數
2、將控制器的作用域從單例改為原型,即在spring配置檔案Controller中宣告 scope="prototype",每次都建立新的controller
3、在Controller中使用ThreadLocal變數

這幾種做法有好有壞,第一種,需要開發人員擁有較高的程式設計水平與思想意識,在編碼過程中力求避免出現這種BUG,而第二種則是容器自動的對每個請求產生一個例項,由JVM進行垃圾回收,因此做到了執行緒安全。
使用第一種方式的好處是例項物件只有一個,所有的請求都呼叫該例項物件,速度和效能上要優於第二種,不好的地方,就是需要程式設計師自己去控制例項變數的狀態保持問題。第二種由於每次請求都建立一個例項,所以會消耗較多的記憶體空間。
所以在使用spring開發web 時要注意,預設Controller、Dao、Service都是單例的