1.SpringMVC默认是单例的,使用成员变量在并发状态下该成员变量的值是被共享的
测试平台 我们目前正在开发的电商项目 (架构组成SpringCloud + SpringBoot + Spring + SpringMVC + Mybatis)
测试说明 构造两个并发访问的请求,它们都会使用一个成员变量,其中一个请求会执行一段耗时15秒左右的for循环的代码,另外一个请求不会执行这段代码,但会修改成员变量的值
测试过程 第一个请求先访问,在执行for循环时紧接着第二个请求访问,第二个请求会马上执行完并且改变成员变量的值,最后看前面这个请求执行完15秒的for循环后再使用这个成员变量时,值有没有改变。
测试源码
@RestController @RequestMapping("/service") public class SurvivalGoldReceiveController extends BaseController { private String nowDate = new SimpleDateFormat("yyyy-MM-dd HH:MM:ss").format(new Date()); @RequestMapping(value = "/toSurvivalGoldReceive") public ModelAndView toSurvivalGoldReceive(){ HttpServletRequest request = this.getRequest(); GeCustomer geCustomer = (GeCustomer)request.getSession().getAttribute(UserPersonalConstant.LOGIN_USER); logger.info("start--nowDate = " + geCustomer.getCustomeraccount() + nowDate); logger.info("其它请求进来后"); if ("youyuqi02".equals(geCustomer.getCustomeraccount())) { StringBuilder sb = new StringBuilder(); long startTime = System.currentTimeMillis(); for (int i = 0; i < 100000000; i++) { sb.append("测试SpringMVC中使用成员变量" + i); sb.append("测试SpringMVC中使用成员变量" + i); sb.append("测试SpringMVC中使用成员变量" + i); sb.append("测试SpringMVC中使用成员变量" + i); sb.append("测试SpringMVC中使用成员变量" + i); sb.delete(0,sb.length()); } long estimatedTime = System.currentTimeMillis() - startTime; logger.info("执行一亿次字符串的拼接与删除耗时 : " + String.valueOf(estimatedTime).substring(0,2) + "秒"); //1s=1000毫秒=1000000微秒=1000000000纳秒 }else{ nowDate = "成员变量被其它请求改变了"; } logger.info("end--nowDate = " + geCustomer.getCustomeraccount() + nowDate); if(geCustomer == null){ return go("/vipcenter/home"); }else{ return go("/vipcenter/survivalGoldReceive/survivalGoldReceiveList"); } } }
测试结果:第二个请求将成员变量nowDate的值改为了字符串“成员变量被其它请求改变了”,第一个请求再使用这个成员变量时,值已经发生了改变
总结:SpringMVC在单例情况下发生并发时会修改成员变量的值,所以慎用成员变量
2.设置controller为多例,@Scope("prototype")
测试源码
@RestController @Scope("prototype") @RequestMapping("/service") public class SurvivalGoldReceiveController extends BaseController { private String nowDate = new SimpleDateFormat("yyyy-MM-dd HH:MM:ss").format(new Date()); @RequestMapping(value = "/toSurvivalGoldReceive") public ModelAndView toSurvivalGoldReceive(){ HttpServletRequest request = this.getRequest(); GeCustomer geCustomer = (GeCustomer)request.getSession().getAttribute(UserPersonalConstant.LOGIN_USER); logger.info("start--nowDate = " + geCustomer.getCustomeraccount() + nowDate); logger.info("其它请求进来后"); if ("youyuqi02".equals(geCustomer.getCustomeraccount())) { StringBuilder sb = new StringBuilder(); long startTime = System.currentTimeMillis(); for (int i = 0; i < 100000000; i++) { sb.append("测试SpringMVC中使用成员变量" + i); sb.append("测试SpringMVC中使用成员变量" + i); sb.append("测试SpringMVC中使用成员变量" + i); sb.append("测试SpringMVC中使用成员变量" + i); sb.append("测试SpringMVC中使用成员变量" + i); sb.delete(0,sb.length()); } long estimatedTime = System.currentTimeMillis() - startTime; logger.info("执行一亿次字符串的拼接与删除耗时 : " + String.valueOf(estimatedTime).substring(0,2) + "秒"); //1s=1000毫秒=1000000微秒=1000000000纳秒 }else{ nowDate = "成员变量被其它请求改变了"; } logger.info("end--nowDate = " + geCustomer.getCustomeraccount() + nowDate); if(geCustomer==null){ return go("/vipcenter/home"); }else{ return go("/vipcenter/survivalGoldReceive/survivalGoldReceiveList"); } } }
测试结果 第一个请求最后获取的成员变量的值并未改变
总结 第二种是多例的,每次访问会创建一个Controller对象,属于线程安全的,但会过多的耗费内存,第一种是单例的,只会创建一次对象,在并发情况下会修改成员变量的值,但会节省内存空间,所以,老司机都用第一种,但会有意识的避免使用成员变量。
参考文献 springMVC 谨慎使用成员变量https://blog.csdn.net/panda_in5/article/details/78528762