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