spring mvc Controller单例解析

Spring MVC 的 Controller 默认为单例,可能导致线程不安全。通过测试发现,即使改为原型模式,类变量仍可能不安全。文章讨论了 Spring 中的 Bean 创建与单例模式实现,并提醒在 Controller 中避免使用线程共享变量,以确保应用的正确运行。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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时进行调用

单例模式、双检测锁定DCL、volatile详解

由于网上已经有源码解析了,我这里就不写了 ???。
由Spring框架中的单例模式想到的

总结

其实,controller中线程共享的变量都不是线程安全的(如类遍历),以及单例模式下的实例变量。如果我们想让每个线程都有自己的一份变量的话,就将该变量定义为类变量,且在Controller类上面使用@Scope(“prototype”)注解。

参考文献

Spring MVC Controller单例陷阱(这个的单例时的结果似乎写错了,正确的可以参考一些 SpringMVC Controller单例和多例)
controller,action,servlet的线程安全问题
SpringMVC Controller单例和多例
由Spring框架中的单例模式想到的
Spring5源码解析-Spring框架中的单例和原型bean

寻找jdk中的设计模式之单例模式

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值