版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/seesun2012/article/details/84719960
问题分析:
- 众所周知,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
你的提问是小编创作灵感的来源!