【问题标题】:Must Spring component classes be thread-safeSpring 组件类必须是线程安全的
【发布时间】:2013-05-28 14:59:26
【问题描述】:

如果您使用 Spring,您的组件类(@Controller@Service@Repository)必须是线程安全的吗?还是 Spring 以线程安全的方式使用它们,因此您不必担心线程安全?

也就是说,如果我的@Controller 中有一个@RequestMapping 方法,是否可以由多个线程同时为同一个控制器对象调用该方法?

(这里有sort-of been asked before,但没有这样回答)。

【问题讨论】:

  • 关于编辑。对于@RequestMapping 要求的格式相同的请求,Spring 将在同一个@Controller 类实例上调用相同的方法。

标签: java spring thread-safety


【解决方案1】:

给定

@Controller
public class MyController {
    @RequestMapping(value = "/index")
    public String respond() {
        return "index";
    }
}

Spring 将创建一个MyController 的实例。这是因为 Spring 会解析您的配置 <mvc:annotation-driven>,看到 @Controller(类似于 @Component)并实例化带注释的类。因为它也看到了@RequestMapping,所以它会为它生成一个HandlerMapping,参见docs here

DispatcherServlet 接收到的任何 HTTP 请求都将通过之前注册的 HandlerMapping 分派到此控制器实例,并通过该实例上的 java 反射调用 respond()

如果你有像这样的实例字段

@Controller
public class MyController {
    private int count = 0;
    @RequestMapping(value = "/index")
    public String respond() {
        count++;
        return "index";
    }
}

count 将是一个危险,因为它可能被许多线程修改并且对它的更改可能会丢失。

您需要了解 Servlet 容器的工作原理。容器实例化你的 Spring MVC DispatcherServlet 的一个实例。容器还管理一个线程池,用于响应连接,即。 HTTP 请求。当这样的请求到达时,容器从池中选择一个线程,并在该线程中执行 DispatcherServlet 上的 service() 方法,该方法将调度到 Spring 为您注册的正确的 @Controller 实例(来自您的配置) .

所以是的,Spring MVC 类必须是线程安全的。您可以通过为类实例字段使用不同的范围或仅使用局部变量来做到这一点。否则,您需要在代码中的关键部分周围添加适当的同步。

【讨论】:

  • “Spring 将创建 MyController 的一个实例”(假设您的意思是只有一个实例),您能否提供有关此声明的任何官方文档?正如我在其他答案中评论的那样,这部分似乎有些混乱。
  • 官方docs,但不是具体地点。让我更新答案。
  • @Nambari this paragraph in docs 明确表示:The singleton scope is the default scope in Spring
  • "你需要了解 Servlet 容器是如何工作的" 这是关键段落; Servlet 容器是多线程的,因此请求处理代码(如 Spring MVC)必须是线程安全的。
  • @Raedwald 看到最后一段,是的。这取决于上下文,请参阅我的示例。
【解决方案2】:

默认情况下,控制器是单例的,因此必须是线程安全的。但是,您可以将控制器配置为请求或会话范围,即:

@Controller
@Scope("session")
public class MyController {

    ...
}

具有会话范围的控制器有助于管理会话状态。在Using Sessions in Spring-MVC (including "scoped-proxies")How to get Session Object In Spring MVC 中可以找到对不同模式的良好描述。一些呈现的模式需要请求范围

如果您拥有无法为每个请求计算多个数据的数据,那么请求范围也很有用。

【讨论】:

  • 你好。我来到这里,发现这很有用。但是你能告诉我,如果有很多用户说成百上千。这是否意味着我们将拥有这么多数量的控制器?请帮忙! @codo
  • 我想是的。否则它将无法正常工作。如果您需要会话数据,您将不可避免地在内存中为每个用户提供数据结构。
【解决方案3】:

是的,当然。

最好是无状态的,默认情况下它们是线程安全的。如果没有共享的可变状态,则没有问题。

【讨论】:

  • “当然”:我认为这对初学者来说并不那么明显。想扩大一点吗?
  • @Raedwalk Spring 仅创建您的 @Component 类的一个实例(默认情况下),因此它们的所有实例字段将为每个请求共享。
  • Spring 默认使 bean 成为单例,但也有其他范围:原型、每个请求、每个会话。其他有经验的开发人员可能会做出不同的选择。我更喜欢无状态的单例,因为我认为它们的扩展性更好。
  • “如果没有共享的可变状态”几乎所有有趣的 Web 应用程序都有共享的可变数据。在大多数情况下,共享数据在数据库中,因此确保线程安全的工作是在 Hibernate/JPA 中完成的。
  • 大多数有趣的 Web 应用程序没有共享状态。它们在会话中保持会话特定信息的安全,只有特定用户才能看到它。我认为您不了解线程安全的含义。
【解决方案4】:

基本上答案应该是肯定的和否定的。除了非常严重的原因。不是因为 Spring 会为您同步工作——它不会这样做(默认情况下,Controller 是 Singleton bean)。粗略的线程安全必须在方法调用中保持,但通常 Servlets 机制消除了同步某些东西的必要性,因为请求在线程内执行。因此,在调用任何 @RequestMapping 注释方法期间,整个调用堆栈都在一个线程中执行。在根中,它从 Servlet 的 service-do(Get, Post..) 方法传出,然后执行处理程序映射,这是 Spring 构建的(例如,参见 http://www.studytrails.com/frameworks/spring/spring-mvc-handler-mappings/http://technicalstack.com/dispatcher-servlethandlermapping-controller/)。在 URL 解析后,Spring 从处理程序映射调用处理方法。没有其他技巧。所以想一想,你在一个 doPost(...) 中工作,例如 DispatchServlet 的实例方法。 Servlet 线程安全吗?没有粗的。 你能做到线程安全吗?是的,使用锁,同步,还有什么让你的 Servlet 成为瓶颈!控制器也是如此。 Servlet 的 doGet/Post/what else 方法基本上都有一个功能模型,所有数据都在 HttpServletRequest 对象中。在控制器对象使用的数据类和堆栈而不是字段中应该使用相同的方式。您可以同步对它们的访问,但要付出瓶颈的代价。粗略的你可以使用原子,它是如此需要吗? 如果您需要在会话期间保持任何状态,您可以在 @Controller 之后使用 @Scope("session") 或(对于单例控制器)带有签名 (..., HttpSession) 的处理程序方法,它们都有优点和缺点。但请注意,您创建了额外的 CPU 和 GC 费用。 无论如何,当您想使用字段并退出无状态概念时,您需要负责控制器线程安全。通常,缓存(例如 redis)对于客户端状态保持更可取。至少您可以在发生故障时恢复状态。超出会话范围的有状态控制器基本上没有理由。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2013-02-15
    • 2018-04-29
    • 1970-01-01
    • 1970-01-01
    • 2016-05-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多