【Spring】bean的做用域(@Scope) - singleton、prototype

  已知spring 3+已擁有多種不一樣的做用域: singleton(默認)、prototype、request、session、global session。(參考: spring中scope做用域(轉))html

  到目前爲止,其實還沒在項目中實際遇到要修改做用域的狀況。java

  但卻知道有大概相似這麼一種說法: spring的bean中不容許(或不建議)定義成員變量,不論是public仍是privatespring

  但以前在作一個功能的時候確實遇到了想在service定義一個成員變量Map類型的,但有映像spring中默認是單例,結合單例的特性。考慮到可能定義成員變量有問題,因此就從新回來看一下。安全

  (最後也沒采用定義成員變量的方式,仍是用的參數傳遞。)  session

1、測試singleton、prototype的差別

  1.1 singleton主要測試代碼
@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

  1.2 prototype主要測試代碼
@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}。

  1.3 結論

    實際的結果和猜測徹底符合,就是簡單的單例/多例的區別。

    因此若是存在相似的代碼:

@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(...);
        // 一些邏輯
    }
}

2、此文的真正目的

  簡單目的: 在spring默認狀況下的bean中定義成員變量帶來的風險。

  但,實際上是記錄spring是怎麼解決線程安全的。(詳見: Spring單例與線程安全小結)

  我我的對線程也不是足夠了解,去零零碎碎看過,但實際項目中確實還沒徹底真正的結果過。

  但在之前看過過一篇文章(就是上面那篇),寫spring是怎麼解決線程安全的,寫spring利用的不是線程同步機制(synchronized)而是用的ThreadLocal。

  這隻記錄下二者的差別,主要仍是上面那篇博客中說的。

  synchronized: 時間換空間,以較慢的執行時間來節約空間的被佔用。

在同步機制中,經過對象的鎖機制保證同一時間只有一個線程訪問變量。這時該變量是多個線程共享的,使用同步機制要求程序慎密地分析何時對變量進行讀寫,何時須要鎖定某個對象,何時釋放對象鎖等繁雜的問題,程序設計和編寫難度相對較大。

  ThreadLocal: 空間換時間,佔用更多的空間來換取較快的執行時間。

在ThreadLocal中,會爲每個線程提供一個獨立的變量副,從而隔離了多個線程對數據的訪問衝突。由於每個線程都擁有本身的變量副本,從而也就沒有必要對該變量進行同步了。ThreadLocal提供了線程安全的共享對象,在編寫多線程代碼時,能夠把不安全的變量封裝進ThreadLocal。

 

  ps: 最近很長一段時間一直至關煩躁,此文章原本很簡單,但寫到最後我本身都不知道本身到底想表達什麼、想記錄什麼....感受更像應付式的給本身一個任務,形式通常的寫一篇文章....

相關文章
相關標籤/搜索