已知spring 3+已擁有多種不一樣的做用域: singleton(默認)、prototype、request、session、global session。(參考: spring中scope做用域(轉))html
到目前爲止,其實還沒在項目中實際遇到要修改做用域的狀況。java
但卻知道有大概相似這麼一種說法: spring的bean中不容許(或不建議)定義成員變量,不論是public仍是private。spring
但以前在作一個功能的時候確實遇到了想在service定義一個成員變量Map類型的,但有映像spring中默認是單例,結合單例的特性。考慮到可能定義成員變量有問題,因此就從新回來看一下。安全
(最後也沒采用定義成員變量的方式,仍是用的參數傳遞。) session
@Controller @Scope("singleton") public class SingletonController { @Autowired private SingletonService singletonService; private Integer controllerIndex = 1; @RequestMapping("/singleton") @ResponseBody public Map<String, Object> singleton(){ Map<String, Object> rs = new HashMap<>(); rs.put("service_index",singletonService.getIndex()); rs.put("controller_index",controllerIndex); rs.put("controller_hashCode",this.hashCode()); rs.put("service_hashCode",singletonService.hashCode()); rs.put("cache",singletonService.getCache()); return rs; } }
@Service @Scope("singleton") public class SingletonService { private Map<String,Object> cache = new HashMap<>(); private Integer index = 1; public Map<String, Object> getCache() { return cache; } public Integer getIndex() { cache.put("index-"+index,index); return index++; } }
結果猜測:多線程
1) 每次請求後controller_index、service_index都在遞增。併發
2) controller_hashCode、service_hashCode每次都保持不變,由於單例只有一個實例。(java中得不到內存地址,變相的hashCode在必定狀況下能夠表示內存)app
3) cache的key/value一直在增多,請求一次多一個。測試
這些所有都符合單例的特性。this
@Controller @Scope("prototype") public class PrototypeController { @Autowired private PrototypeService prototypeService; private Integer controllerIndex = 1; @RequestMapping("/prototype") @ResponseBody public Map<String, Object> singleton(){ Map<String, Object> rs = new HashMap<>(); rs.put("service_index",prototypeService.getIndex()); rs.put("controller_index",controllerIndex); rs.put("controller_hashCode",this.hashCode()); rs.put("service_hashCode",prototypeService.hashCode()); rs.put("cache",prototypeService.getCache()); return rs; } }
@Service @Scope("prototype") public class PrototypeService { private Map<String,Object> cache = new HashMap<>(); private Integer index = 1; public Map<String, Object> getCache() { return cache; } public Integer getIndex() { cache.put("index-"+index,index); return index++; } }
結果猜測:
1) controller_index、service_index始終都是1。
2) controller_hashCode、service_hashCode每次都在改變。
3) cache只有一個key/value,即{"index-1": 1}。
實際的結果和猜測徹底符合,就是簡單的單例/多例的區別。
因此若是存在相似的代碼:
@Service @Scope("singleton") public class SingletonService { private Map<String,Object> cache = null; public void aMethod() { cache = new HashMap<>(); // 一些邏輯 cache.put(...); bMethod(); } public void bMethod() { Object obj = cache.get(...); // 一些邏輯 } }
想如今aMethod()中處理一些邏輯,而後把符合的保存起來供bMethod()使用。而且你簡單的測試不會有問題,由於是單線程的測試。
但如今總體來看,若是多個用戶同時操做。原本A是put(1,"a"),可是此時B又緊接着put(1,"b")。A先進入bMethod(),那麼get(1) = "b",這就是明顯的多線程併發問題。
固然能夠把Scope改爲prototype,但如今的處境是: 1) 沒絕對的比較。2) 整個項目中並無用prototype的先例。
因此最多見的做法是改爲方法傳參:
@Service @Scope("singleton") public class SingletonService { public void aMethod() { private Map<String,Object> cache = new HashMap<>(); // 一些邏輯 cache.put(...); bMethod(); } public void bMethod(Map<String,Object> cache) { Object obj = cache.get(...); // 一些邏輯 } }
簡單目的: 在spring默認狀況下的bean中定義成員變量帶來的風險。
但,實際上是記錄spring是怎麼解決線程安全的。(詳見: Spring單例與線程安全小結)
我我的對線程也不是足夠了解,去零零碎碎看過,但實際項目中確實還沒徹底真正的結果過。
但在之前看過過一篇文章(就是上面那篇),寫spring是怎麼解決線程安全的,寫spring利用的不是線程同步機制(synchronized)而是用的ThreadLocal。
這隻記錄下二者的差別,主要仍是上面那篇博客中說的。
synchronized: 時間換空間,以較慢的執行時間來節約空間的被佔用。
在同步機制中,經過對象的鎖機制保證同一時間只有一個線程訪問變量。這時該變量是多個線程共享的,使用同步機制要求程序慎密地分析何時對變量進行讀寫,何時須要鎖定某個對象,何時釋放對象鎖等繁雜的問題,程序設計和編寫難度相對較大。
ThreadLocal: 空間換時間,佔用更多的空間來換取較快的執行時間。
在ThreadLocal中,會爲每個線程提供一個獨立的變量副本,從而隔離了多個線程對數據的訪問衝突。由於每個線程都擁有本身的變量副本,從而也就沒有必要對該變量進行同步了。ThreadLocal提供了線程安全的共享對象,在編寫多線程代碼時,能夠把不安全的變量封裝進ThreadLocal。
ps: 最近很長一段時間一直至關煩躁,此文章原本很簡單,但寫到最後我本身都不知道本身到底想表達什麼、想記錄什麼....感受更像應付式的給本身一個任務,形式通常的寫一篇文章....