controller单例模式
单例模式(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.
在Tomcat容器中,每个servlet是单例的。在SpringMVC中,Controller 默认也是单例。采⽤单例模式的最⼤好处,就是可以在⾼并发场景下极⼤地节省内存资源,提⾼服务抗压能⼒。
单例模式容易出现的问题是:在Controller中定义的实例变量,在多个请求并发时会出现竞争访问,Controller中的实例变量不是线程安全的。
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);
}
}
在本地运⾏后:
⾸先访问 localhost:8080 / addNum,得到的答案是1;
再次访问 localhost:8080 / addNum,得到的答案是 2。
两次访问得到的结果不同,num已经被修改,并不是我们希望的结果,接⼝的幂等性被破坏。
从这个例⼦可以看出,所有的请求访问同⼀个Controller实例,Controller的私有成员变量就是线程共⽤的。某个请求对应的线程如果修改了这个变量,那么在别的请求中也可以读到这个变量修改后的的值。
Controller并发安全的解决办法
如果要保证Controller的线程安全,有以下解决办法:
尽量不要在 Controller 中定义成员变量;
如果必须要定义⼀个⾮静态成员变量,那么可以通过注解 @Scope(“prototype”) ,将Controller设置为多例模式。
@Controller
@Scope(value="prototype")
public class TestController {
private int num = 0;
mvc的controller
@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.set(++unum);
System.out.());
}
}
更严格的做法是⽤AtomicInteger类型定义成员变量,对于成员变量的操作使⽤AtomicInteger的⾃增⽅法完成。总的来说,还是尽量不要在 Controller 中定义成员变量为好。

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。