起源:https://www.toutiao.com/artic…
单例模式(Singleton)是程序设计中一种十分重要的设计模式,设计模式也是 Java 面试重点考查的一个方面。面试常常会问到的一个问题是:SpringMVC 中的 Controller 是单例还是多例,很多同学可能会想当然认为 Controller 是多例,其实不然。
依据 Tomcat 官网中的介绍,对于一个浏览器申请,tomcat 会指定一个解决线程,或是在线程池中选取闲暇的,或者新建一个线程。
Each incoming request requires a thread for the duration of that request. If more simultaneous requests are received than can be handled by the currently available request processing threads, additional threads will be created up to the configured maximum (the value of the maxThreads attribute). If still more simultaneous requests are received, they are stacked up inside the server socket created by the Connector, up to the configured maximum (the value of the acceptCountattribute). Any further simultaneous requests will receive “connection refused” errors, until resources are available to process them.
—— https://tomcat.apache.org/tom…
在 Tomcat 容器中,每个 servlet 是单例的。在 SpringMVC 中,Controller 默认也是单例。 采纳单例模式的最大益处,就是能够在高并发场景下极大地节俭内存资源,进步服务抗压能力。
单例模式容易呈现的问题是:在 Controller 中定义的实例变量,在多个申请并发时会呈现竞争拜访,Controller 中的实例变量不是线程平安的。
Spring Boot 根底就不介绍了,举荐下这个实战教程:https://github.com/javastacks…
Controller 不是线程平安的
正因为 Controller 默认是单例,所以不是线程平安的。如果用 SpringMVC 的 Controller 时,尽量不在 Controller 中应用实例变量,否则会呈现线程不安全性的状况,导致数据逻辑凌乱。
举一个简略的例子,在一个 Controller 中定义一个非动态成员变量 num。通过 Controller 成员办法来对 num 减少。
@Controller
public class TestController {
private int num = 0;
@RequestMapping("/addNum")
public void addNum() {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")
public class TestController {
private int num = 0;
@RequestMapping("/addNum")
public void addNum() {System.out.println(++num);
}
}
Scope 属性是用来申明 IOC 容器中的对象(Bean)容许存在的限定场景,或者说是对象的存活空间。在对象进入相应的应用场景之前,IOC 容器会生成并拆卸这些对象;当该对象不再处于这些应用场景的限定时,容器通常会销毁这些对象。
Controller 也是一个 Bean,默认的 Scope 属性为Singleton,也就是单例模式。如果 Bean 的 Scope 属性设置为 prototype 的话,容器在承受到该类型对象的申请时,每次都会从新生成一个新的对象给申请方。
- Controller 中应用 ThreadLocal 变量。 每一个线程都有一个变量的正本。
public class TestController {
private int num = 0;
private final ThreadLocal <Integer> uniqueNum =
new ThreadLocal <Integer> () {@Override protected Integer initialValue() {return num;}
};
@RequestMapping("/addNum")
public void addNum() {int unum = uniqueNum.get();
uniqueNum.set(++unum);
System.out.println(uniqueNum.get());
}
}
以上代码运行当前,每次申请 http:// localhost:8080 / addNum , 失去的后果都是 1。
更严格的做法是用 AtomicInteger 类型定义成员变量,对于成员变量的操作应用 AtomicInteger 的自增办法实现。
总的来说,还是尽量不要在 Controller 中定义成员变量为好。
近期热文举荐:
1.1,000+ 道 Java 面试题及答案整顿(2022 最新版)
2. 劲爆!Java 协程要来了。。。
3.Spring Boot 2.x 教程,太全了!
4. 别再写满屏的爆爆爆炸类了,试试装璜器模式,这才是优雅的形式!!
5.《Java 开发手册(嵩山版)》最新公布,速速下载!
感觉不错,别忘了顺手点赞 + 转发哦!