面试官:Spring MVC 如何保证 Controller 的并发安全性?面试必问。。

简介: 单例模式(Singleton)是程序设计中一种非常重要的设计模式,设计模式也是Java面试重点考察的一个方面。

单例模式(Singleton)是程序设计中一种非常重要的设计模式,设计模式也是Java面试重点考察的一个方面。面试经常会问到的一个问题是:SpringMVC中的Controller是单例还是多例,很多同学可能会想当然认为Controller是多例,其实不然。

image.png

根据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/tomcat-7.0-doc/config/http.html

在Tomcat容器中,每个servlet是单例的。在SpringMVC中,Controller 默认也是单例。 采用单例模式的最大好处,就是可以在高并发场景下极大地节省内存资源,提高服务抗压能力。


单例模式容易出现的问题是:在Controller中定义的实例变量,在多个请求并发时会出现竞争访问,Controller中的实例变量不是线程安全的。


Spring Boot 基础就不介绍了,推荐下这个实战教程:https://github.com/javastacks/spring-boot-best-practice

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 中定义成员变量为好。




目录
打赏
0
0
0
0
309
分享
相关文章
微服务——SpringBoot使用归纳——Spring Boot中的MVC支持——@RequestParam
本文介绍了 `@RequestParam` 注解的使用方法及其与 `@PathVariable` 的区别。`@RequestParam` 用于从请求中获取参数值(如 GET 请求的 URL 参数或 POST 请求的表单数据),而 `@PathVariable` 用于从 URL 模板中提取参数。文章通过示例代码详细说明了 `@RequestParam` 的常用属性,如 `required` 和 `defaultValue`,并展示了如何用实体类封装大量表单参数以简化处理流程。最后,结合 Postman 测试工具验证了接口的功能。
53 0
微服务——SpringBoot使用归纳——Spring Boot中的MVC支持——@RequestParam
微服务——SpringBoot使用归纳——Spring Boot中的MVC支持——@RequestBody
`@RequestBody` 是 Spring 框架中的注解,用于将 HTTP 请求体中的 JSON 数据自动映射为 Java 对象。例如,前端通过 POST 请求发送包含 `username` 和 `password` 的 JSON 数据,后端可通过带有 `@RequestBody` 注解的方法参数接收并处理。此注解适用于传递复杂对象的场景,简化了数据解析过程。与表单提交不同,它主要用于接收 JSON 格式的实体数据。
65 0
微服务——SpringBoot使用归纳——Spring Boot中的MVC支持——@PathVariable
`@PathVariable` 是 Spring Boot 中用于从 URL 中提取参数的注解,支持 RESTful 风格接口开发。例如,通过 `@GetMapping(&quot;/user/{id}&quot;)` 可以将 URL 中的 `{id}` 参数自动映射到方法参数中。若参数名不一致,可通过 `@PathVariable(&quot;自定义名&quot;)` 指定绑定关系。此外,还支持多参数占位符,如 `/user/{id}/{name}`,分别映射到方法中的多个参数。运行项目后,访问指定 URL 即可验证参数是否正确接收。
44 0
微服务——SpringBoot使用归纳——Spring Boot中的MVC支持——@RequestMapping
@RequestMapping 是 Spring MVC 中用于请求地址映射的注解,可作用于类或方法上。类级别定义控制器父路径,方法级别进一步指定处理逻辑。常用属性包括 value(请求地址)、method(请求类型,如 GET/POST 等,默认 GET)和 produces(返回内容类型)。例如:`@RequestMapping(value = &quot;/test&quot;, produces = &quot;application/json; charset=UTF-8&quot;)`。此外,针对不同请求方式还有简化注解,如 @GetMapping、@PostMapping 等。
45 0
微服务——SpringBoot使用归纳——Spring Boot中的MVC支持——@RestController
本文主要介绍 Spring Boot 中 MVC 开发常用的几个注解及其使用方式,包括 `@RestController`、`@RequestMapping`、`@PathVariable`、`@RequestParam` 和 `@RequestBody`。其中重点讲解了 `@RestController` 注解的构成与特点:它是 `@Controller` 和 `@ResponseBody` 的结合体,适用于返回 JSON 数据的场景。文章还指出,在需要模板渲染(如 Thymeleaf)而非前后端分离的情况下,应使用 `@Controller` 而非 `@RestController`
34 0
对Spring、SpringMVC、MyBatis框架的介绍与解释
Spring 框架提供了全面的基础设施支持,Spring MVC 专注于 Web 层的开发,而 MyBatis 则是一个高效的持久层框架。这三个框架结合使用,可以显著提升 Java 企业级应用的开发效率和质量。通过理解它们的核心特性和使用方法,开发者可以更好地构建和维护复杂的应用程序。
163 29
步步深入SpringMvc DispatcherServlet源码掌握springmvc全流程原理
通过对 `DispatcherServlet`源码的深入剖析,我们了解了SpringMVC请求处理的全流程。`DispatcherServlet`作为前端控制器,负责请求的接收和分发,处理器映射和适配负责将请求分派到具体的处理器方法,视图解析器负责生成和渲染视图。理解这些核心组件及其交互原理,有助于开发者更好地使用和扩展SpringMVC框架。
83 4
Spring MVC中的请求映射:@RequestMapping注解深度解析
在Spring MVC框架中,`@RequestMapping`注解是实现请求映射的关键,它将HTTP请求映射到相应的处理器方法上。本文将深入探讨`@RequestMapping`注解的工作原理、使用方法以及最佳实践,为开发者提供一份详尽的技术干货。
357 2
如何实现Spring Boot应用程序的安全性
如何实现Spring Boot应用程序的安全性
150 0
SSM:SpringMVC
本文介绍了SpringMVC的依赖配置、请求参数处理、注解开发、JSON处理、拦截器、文件上传下载以及相关注意事项。首先,需要在`pom.xml`中添加必要的依赖,包括Servlet、JSTL、Spring Web MVC等。接着,在`web.xml`中配置DispatcherServlet,并设置Spring MVC的相关配置,如组件扫描、默认Servlet处理器等。然后,通过`@RequestMapping`等注解处理请求参数,使用`@ResponseBody`返回JSON数据。此外,还介绍了如何创建和配置拦截器、文件上传下载的功能,并强调了JSP文件的放置位置,避免404错误。