【開源框架】spring IOC 容器中的單例,高併發情況下成員變量不被影響原因及原理

問題分析:

  • 衆所周知,SpringMVC 中的 Controller 是存放在IOC容器中的,由於 SpringICO 中存放的都是單例模式對象,當多個請求瞬間同時從 Servlet 進入 Controller 時就會產生全局變量被修改的線程安全問題(spring已經解決了這個問題,這裏分析的是怎麼解決)。

spring的解決方案:

  • 創建一個副本去執行(重新實例化),避免多個線程對一個全局變量產生修改(在堆內存建立一個獨立對象)

  • 只要是用new()來新建對象的,都會在堆中創建,而且其字符串是單獨存值的,即使與棧中的數據相同,也不會與棧中的數據共享

1、模擬SpringICO測試案例:

package com.seesun2012.spring.test;

import java.lang.reflect.Method;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * @title:     SpringICO測試類
 * @version    v1.0.0
 * @author     csdn:seesun2012
 * @date       2018年12月02日  下午15:51:44  週日
 *
 */
public class SpringICO {

    // 全局變量  (這裏不可爲靜態變量,新手坑誤踩,大神請忽略)
    private Integer ssa = 0;
    //  設定ICO容器,將實例化的對象緩存進IOC中
    private static Map<String, Object> ioc = new HashMap<String, Object>();
    // 設定請求映射器
    private static Map<String, Method> handleMapping = new HashMap<String, Method>();
    // 定義維持5個數量的線程池
    private static final ExecutorService service = Executors.newFixedThreadPool(5);

    /**
     * IOC初始化(例如servlet初始化)
     */
    public static void init() throws Exception{
        //  獲取當前類,也可以做成掃描 @Controller註解 方式獲取(註解+反射)
        Class clazz = new SpringICO().getClass();
        //  將路徑爲 SpringICO/doGet 對象以單例的方式注入到IOC容器
        ioc.put(clazz.getSimpleName(), clazz.newInstance());
        //  獲取對象的方法,也可以做成掃描 @RequestMapping註解 方式獲取(註解+反射)
        Method method = clazz.getMethod("doGet", String.class);
        //  將對象方法名稱與方法進行關係映射
        handleMapping.put(clazz.getSimpleName() + "/doGet", method);
    }

    /**
     * 請求分發
     * @param url       請求路徑
     * @param params    請求參數
     */
    public static Object service(String url, String params) {
        Object str = "線程" + Thread.currentThread().getName() + ",輸出參數:";
        try {
            //  解析請求路徑
            String[] urlArr = url.split("/");
            //  通過動態代理執行請求過來的方法(執行前會創建一個副本)
            Object retr = handleMapping.get(url).invoke(ioc.get(urlArr[0]).getClass().newInstance(), params);
            str = str + retr.toString();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return str;
    }

    /**
     * 請求執行器
     */
    public Integer doGet(String str) {
        //  設定每次進入的請求都將全局變量ssa+1(問題所在)
        return ++ssa;
    }

    public static void main(String[] args) throws Exception{
        //  啓動初始化
        init();
        //  打印請求路徑
        for (Map.Entry<String, Method> entry: handleMapping.entrySet()) {
            System.out.println("init() INFO URL:--------------" + entry.getKey() + "--------------");
        }
        //  設定多線程同時併發訪問
        for(int i = 0 ; i < 10 ; i++){
            //  提交線程到線程池
            service.submit(new Runnable() {
                @Override
                public void run() {
                    System.out.println(service("SpringICO/doGet", ""));
                }
            });
        }
        //  線程池計數,當service.submit()到最後一個線程時啓動執行,即0
        service.shutdown();
    }

}

【創建副本】解決方案:
關鍵代碼,在於Class.newInstance()

Object retr = handleMapping.get(url).invoke(ioc.get(urlArr[0]).getClass().newInstance(), params);

測試結果:

init() INFO URL:--------------SpringICO/doGet--------------
線程pool-1-thread-2,輸出參數:1
線程pool-1-thread-3,輸出參數:1
線程pool-1-thread-1,輸出參數:1
線程pool-1-thread-2,輸出參數:1
線程pool-1-thread-3,輸出參數:1
線程pool-1-thread-5,輸出參數:1
線程pool-1-thread-4,輸出參數:1
線程pool-1-thread-3,輸出參數:1
線程pool-1-thread-2,輸出參數:1
線程pool-1-thread-1,輸出參數:1

【無副本】測試:
關鍵代碼,將54行代碼修改爲:

Object retr = handleMapping.get(url).invoke(ioc.get(urlArr[0]), params);

測試結果:

init() INFO URL:--------------SpringICO/doGet--------------
線程pool-1-thread-3,輸出參數:1
線程pool-1-thread-5,輸出參數:2
線程pool-1-thread-3,輸出參數:3
線程pool-1-thread-5,輸出參數:4
線程pool-1-thread-3,輸出參數:5
線程pool-1-thread-5,輸出參數:6
線程pool-1-thread-3,輸出參數:7
線程pool-1-thread-4,輸出參數:8
線程pool-1-thread-1,輸出參數:8
線程pool-1-thread-2,輸出參數:9



持續更新中…

如有對思路不清晰或有更好的解決思路,歡迎與本人交流,QQ羣:273557553,個人微信:seesun2012

你的提問是小編創作靈感的來源!
































發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章