当前位置: 首页 > 后端技术 > Java

面试官:SpringMVC是如何保证Controller的并发安全的?面试必问,.

时间:2023-04-01 16:46:39 Java

来源:https://www.toutiao.com/artic...单例模式(Singleton)是编程中非常重要的一种设计模式,设计模式也是Java面试重点的一个方面。面试中经常被问到的一个问题是:SpringMVC中的Controller是单实例还是多实例?很多同学可能想当然地认为Controller是多实例的,其实不然。根据Tomcat官网的介绍,对于一个浏览器请求,tomcat会指定一个处理线程,或者在线程池中选择一个空闲线程,或者新建一个线程。每个传入请求在该请求期间都需要一个线程。如果同时接收到的请求多于当前可用的请求处理线程可以处理的数量,则将创建额外的线程,直到达到配置的最大值(maxThreads属性的值)。如果同时接收到更多请求,它们将堆积在连接器创建的服务器套接字内,直到达到配置的最大值(acceptCount属性的值)。任何进一步的同时请求将收到“连接被拒绝”错误,直到资源处理它们。——https://tomcat.apache.org/tom...在Tomcat容器中,每个servlet都是一个单例。在SpringMVC中,Controller默认也是单例的。使用单例模式最大的好处就是在高并发场景下可以大大节省内存资源,提高服务的抗压能力。单例模式容易出现的问题是:Controller中定义的实例变量在多个请求并发时会竞争访问,而Controller中的实例变量不是线程安全的。我不会介绍SpringBoot的基础知识。推荐这个实用教程:https://github.com/javastacks...Controller不是线程安全的只是因为Controller默认是单例的,所以不是线程安全的。如果使用SpringMVC的Controller,尽量不要在Controller中使用实例变量,否则会出现线程不安全,导致数据逻辑混乱。举个简单的例子,在Controller中定义一个非静态成员变量num。通过Controller成员方法增加num。@ControllerpublicclassTestController{privateintnum=0;@RequestMapping("/addNum")publicvoidaddNum(){System.out.println(++num);}}在本地运行后:首先访问http://localhost:8080/addNum,答案为1;再次访问http://localhost:8080/addNum,答案为2。两次访问的结果不同,修改了num,不是我们预期的结果,破坏了接口的幂等性。从这个例子可以看出,所有的请求都访问同一个Controller实例,Controller的私有成员变量是线程共享的。如果某个请求对应的线程修改了这个变量,在其他请求中也可以读取到这个变量修改后的值。Controller并发安全的解决方案如果要保证Controller的线程安全,有以下解决方案:Controller中尽量不要定义成员变量;如果一定要定义非静态成员变量,可以使用注解@Scope("prototype"),将Controller设置为多实例模式。@Controller@Scope(value="prototype")publicclassTestController{privateintnum=0;@RequestMapping("/addNum")publicvoidaddNum(){System.out.println(++num);}}Scope属性用于声明IOC容器中允许对象(Bean)存在的限定场景,或者说对象的生存空间。IOC容器在对象进入合适的使用场景之前生成并组装对象;当对象不再受这些使用场景的约束时,容器通常会销毁对象。Controller也是一个Bean,默认的Scope属性是Singleton,也就是单例模式。如果Bean的Scope属性设置为prototype,那么容器在每次收到对此类对象的请求时,都会重新生成一个新的对象给请求者。在控制器中使用ThreadLocal变量。每个线程都有一个变量的副本。公共类TestController{privateintnum=0;privatefinalThreadLocaluniqueNum=newThreadLocal(){@OverrideprotectedIntegerinitialValue(){returnnum;}};@RequestMapping("/addNum")publicvoidaddNum(){intunum=uniqueNum.get();uniqueNum.set(++unum);System.out.println(uniqueNum.get());}}上面代码运行后,每次请求http://localhost:8080/addNum,结果都是1。更严格的做法是定义成员变量为AtomicInteger类型,使用AtomicInteger的自增方式来实现完成对成员变量的操作。一般来说,在Controller中尽量不要定义成员变量比较好。近期热点文章推荐:1.1000+Java面试题及答案(2022最新版)2.厉害了!Java协程来了。..3.SpringBoot2.x教程,太全面了!4.不要用爆破爆满画面,试试装饰者模式,这才是优雅的方式!!5.《Java开发手册(嵩山版)》最新发布,赶快下载吧!感觉不错,别忘了点赞+转发!