SpringMvc的Service,Controller默认都是单例的,所以线程不安全的。
测试线程不安全
我用springMVC写了个Controller,然后使用httpClient开启30个线程请求了30次。源码。
// Controller
@Controller
//@Scope("prototype")
public class TestController {
// 类变量
private static int a = 0;
// 实例变量
private int b = 0;
@RequestMapping("/hello")
@ResponseBody
public Map<String, Integer> helloWord(){
System.out.println("类变量:"+(++a)+" | 实例变量:"+(++b));
Map<String,Integer> result = new HashMap<>();
result.put("类变量",a);
result.put("实例变量",b);
return result;
}
}
public static void main( String[] args )
{
Thread thread;
/**
* 开启100个线程测试controller
*/
for(int i=1;i<=30;++i){
thread = new Thread(new Runnable() {
@Override
public void run() {
CloseableHttpClient httpclient = HttpClients.createDefault();
HttpGet httpget = new HttpGet("http://localhost/hello");
CloseableHttpResponse response = null;
try {
response = httpclient.execute(httpget);
// 获取返回的实体
HttpEntity entity = response.getEntity();
// 将实体转为json
String json = EntityUtils.toString(entity);
System.out.println(json);
} catch (IOException e) {
e.printStackTrace();
}
}
});
thread.start();
}
}
然后,我查看Controller控制台打印。
类变量:1 | 实例变量:1
类变量:3 | 实例变量:3
类变量:4 | 实例变量:4
类变量:6 | 实例变量:6
类变量:2 | 实例变量:2
类变量:7 | 实例变量:7
类变量:5 | 实例变量:5
类变量:8 | 实例变量:8
类变量:9 | 实例变量:9
类变量:10 | 实例变量:10
类变量:11 | 实例变量:11
类变量:12 | 实例变量:12
类变量:13 | 实例变量:13
类变量:15 | 实例变量:15
类变量:14 | 实例变量:14
类变量:16 | 实例变量:16
类变量:18 | 实例变量:18
类变量:19 | 实例变量:19
类变量:21 | 实例变量:21
类变量:22 | 实例变量:22
类变量:23 | 实例变量:23
类变量:24 | 实例变量:24
类变量:25 | 实例变量:26
类变量:17 | 实例变量:17
类变量:28 | 实例变量:29
类变量:27 | 实例变量:28
类变量:26 | 实例变量:27
类变量:26 | 实例变量:27
类变量:24 | 实例变量:25
类变量:20 | 实例变量:20
最大值不是30,说明了有些线程获取到了意料之外的数据。controller的单例情况下是线程不安全的(共享的变量都不是线程安全的,这里的a,b)。
然后,我将Controller的//@Scope(“prototype”)
去掉,让controller变为原型模式。意思是每次请求都重新创建一个Controller,那么,实例变量的值应该都变为1;同样,类变量的最大值也可能不为30,说明也不是线程安全的。实际结果如下。
类变量:1 | 实例变量:1
类变量:2 | 实例变量:1
类变量:3 | 实例变量:1
类变量:4 | 实例变量:1
类变量:5 | 实例变量:1
类变量:3 | 实例变量:1
类变量:6 | 实例变量:1
类变量:7 | 实例变量:1
类变量:8 | 实例变量:1
类变量:9 | 实例变量:1
类变量:10 | 实例变量:1
类变量:7 | 实例变量:1
类变量:11 | 实例变量:1
类变量:12 | 实例变量:1
类变量:13 | 实例变量:1
类变量:14 | 实例变量:1
类变量:15 | 实例变量:1
类变量:16 | 实例变量:1
类变量:18 | 实例变量:1
类变量:17 | 实例变量:1
类变量:19 | 实例变量:1
类变量:20 | 实例变量:1
类变量:21 | 实例变量:1
类变量:22 | 实例变量:1
类变量:23 | 实例变量:1
类变量:24 | 实例变量:1
类变量:24 | 实例变量:1
类变量:25 | 实例变量:1
类变量:26 | 实例变量:1
类变量:27 | 实例变量:1
的确不为30,说明共享的变量都不是线程安全的(这里类变量)。但是b是线程安全的。都为1。
源码解析
spring的bean的注入默认都是单例的。是使用双重校验的方式实现单例模式的。
Spring的依赖注入(包括lazy-init方式)都是发生在AbstractBeanFactory的getBean里。getBean的doGetBean方法调用getSingleton进行bean的创建。lazy-init方式,在容器初始化时候进行调用,非lazy-init方式,在用户向容器第一次索要bean时进行调用
由于网上已经有源码解析了,我这里就不写了 ???。
由Spring框架中的单例模式想到的
总结
其实,controller中线程共享的变量都不是线程安全的(如类遍历),以及单例模式下的实例变量。如果我们想让每个线程都有自己的一份变量的话,就将该变量定义为类变量,且在Controller类上面使用@Scope(“prototype”)
注解。
参考文献
Spring MVC Controller单例陷阱(这个的单例时的结果似乎写错了,正确的可以参考一些 SpringMVC Controller单例和多例)
controller,action,servlet的线程安全问题
SpringMVC Controller单例和多例
由Spring框架中的单例模式想到的
Spring5源码解析-Spring框架中的单例和原型bean