文章目录
单例模式Singleton是程序设计中一种非常重要的设计模式,设计模式也是Java面试重点考察的一个方面。面试经常会问到的一个问题是:SpringMVC中的Controller是单例还是多例,很多同学可能会想当然认为Controller是多例,其实不然。
根据Tomcat官网中的介绍,对于一个浏览器请求,tomcat会指定一个处理线程,或是在线程池中选取空闲的,或者新建一个线程。
在Tomcat容器中,每个servlet是单例的。 在SpringMVC中,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);
}
}
在本地运行后:
- 首先访问 http:// localhost:8080 / addNum,得到的答案是1;
- 再次访问 http:// localhost:8080 / addNum,得到的答案是 2。
两次访问得到的结果不同,num已经被修改,并不是我们希望的结果,接口的幂等性被破坏
。
从这个例子可以看出,所有的请求访问同一个Controller实例,Controller的私有成员变量就是线程共用的。某个请求对应的线程如果修改了这个变量,那么在别的请求中也可以读到这个变量修改后的的值。
spring单例,为什么controller、service和dao确能保证线程安全?
Spring中的Bean默认是单例模式的,框架并没有对bean进行多线程的封装处理。
实际上大部分时间Bean是无状态的(比如Dao) 所以说在某种程度上来说Bean其实是安全的。
但是如果Bean是有状态的 那就需要开发人员自己来进行线程安全的保证,最简单的办法就是改变bean的作用域 把 "singleton"改为’‘protopyte’ 这样每次请求Bean就相当于是 new Bean() 这样就可以保证线程的安全了
。
有状态就是有数据存储功能
无状态就是不会保存数据
controller、service和dao层本身并不是线程安全的,只是如果只是调用里面的方法,而且多线程调用一个实例的方法,会在内存中复制变量,这是自己的线程的工作内存,是安全的。
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 的话,容器在接受到该类型对象的请求时,每次都会重新生成一个新的对象给请求方。
加了@Scope注解多的实例prototype是不是一定就是线程安全的呢?
@RestController
@Scope(value = "prototype") // 加上@Scope注解,他有2个取值:单例-singleton 多实例-prototype
public class TestController {
private int var = 0;
private static int staticVar = 0;
@GetMapping(value = "/test_var")
public String test() {
System.out.println("普通变量var:" + (++var)+ "---静态变量staticVar:" + (++staticVar));
return "普通变量var:" + var + "静态变量staticVar:" + staticVar;
}
}
看三次请求结果:
普通变量var:1---静态变量staticVar:1
普通变量var:1---静态变量staticVar:2
普通变量var:1---静态变量staticVar:3
虽然每次都是单独创建一个Controller但是扛不住他变量本身是static的呀,所以说呢,即便是加上@Scope注解也不一定能保证Controller 100%的线程安全。所以是否线程安全在于怎样去定义变量以及Controller的配置。
- 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 中定义成员变量为好。
参考https://zhuanlan.zhihu.com/p/270601243
原文https://zhuanlan.zhihu.com/p/377433109