問題分析:
- 衆所周知,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
你的提問是小編創作靈感的來源!