Spring Boot MVC 控制器是多线程的吗?

Posted

技术标签:

【中文标题】Spring Boot MVC 控制器是多线程的吗?【英文标题】:Is Spring Boot MVC controller multithreaded? 【发布时间】:2015-07-22 03:44:06 【问题描述】:

我是 Spring MVC 的新手,我在 doc 中没有找到任何关于此的内容。 假设我有一个控制器 /accounts 接受 POST 请求来创建一个帐户。 两个请求(几乎)同时使用相同的帐户 ID。 ASFAIK dispatcherServlet 管理请求。

在第一个请求完成之前,第二个请求是否被放入队列? 或者会有两个线程同时处理两个请求?

更新: 查看官方 Spring 教程:Building a REST service。 有方法add的控制器:

@RequestMapping(method = RequestMethod.POST)
ResponseEntity<?> add(@PathVariable String userId, @RequestBody Bookmark input) 
    this.validateUser(userId);
    return this.accountRepository
            .findByUsername(userId)
            .map(account -> 
                Bookmark result = bookmarkRepository.save(new Bookmark(account,
                        input.uri, input.description));

                HttpHeaders httpHeaders = new HttpHeaders();
                httpHeaders.setLocation(ServletUriComponentsBuilder
                        .fromCurrentRequest().path("/id")
                        .buildAndExpand(result.getId()).toUri());
                return new ResponseEntity<>(null, httpHeaders, HttpStatus.CREATED);
            ).get();


控制器只是 bean,默认情况下它们是单例的。 当接收到两个同时请求时,两个线程使用相同的控制器实例。让我们假设第一个线程已经保存了新书签并正在执行

httpHeaders.setLocation(ServletUriComponentsBuilder
                    .fromCurrentRequest().path("/id")
                    .buildAndExpand(result.getId()).toUri());

同时第二个线程刚刚执行

Bookmark result = bookmarkRepository.save(new Bookmark(account,
                    input.uri, input.description));

在这种情况下,第一个线程将返回由第二个线程创建的result.getId()).toUri()

这是正确的工作流程吗?这个控制器线程安全吗?

【问题讨论】:

如果控制器有状态,就会出现线程问题。但它没有。你在这里操作的只是局部变量,每个线程都有自己的、非共享的局部变量。一个线程无法看到另一个线程的局部变量。 【参考方案1】:

大多数 servlet 为每个传入的请求启动一个单独的线程,Spring 也不例外。您需要确保共享 bean 是线程安全的。否则 Spring 会负责其余的工作。

【讨论】:

【参考方案2】:

对于该类型的所有框架,可以安全地假设 控制器方法将同时处理(即在多个并发线程中)。此外,以任何其他方式执行此操作都会严重影响性能(是的,有些框架的设计与从头开始非常不同,但我们现在不讨论这些)。

Spring 本身无法知道 URL 的帐户 id 部分在某种程度上是特殊的,以便在其上进行同步。如果它会在 URL 级别上进行同步,则会将性能降低到爬行状态。您始终可以自己实现此逻辑,例如,在此控制器方法操作的会话范围对象上同步(或使整个控制器会话范围为Lev's answer 建议)。然后,这将仅序列化该用户(该 HTTP 会话)的调用。如果您想在所有用户之间同步,您可以拥有一个全局共享锁,但由于上述原因,这是一个非常糟糕的主意。

相反,应以所有控制器完全无状态的方式设计您的应用程序,并尽可能使方法具有幂等性或具有正确的前提条件检查逻辑。如果该方法正在写入数据库,则可以实施乐观锁定策略。 Hibernate 已经具有该功能,并且某些数据库/驱动程序也具有该功能,因此请查看适用于您的情况的功能。

更新

在提供的示例中,两个线程都在保存一个实例(我假设保存意味着根据需要插入/更新)并且返回的对象是当前保存操作的结果,因此没有奇怪的线程间行为。这是假设保存是事务性的,具有合理的事务隔离级别(所有其他假设都是非常不寻常的)。事实上,第二个线程会简单地覆盖第一个线程写入的值,但这可能是您所期望的。也就是说,您只是在读取下一行中的 id,并且可能永远不会改变,因此 URL 构建似乎不受并发性的影响。

【讨论】:

【参考方案3】:

问题的解决方法可能如下:

    使用@Scope(“request”) 或@Scope(“session”) 注释控制器 将私有变量移动到方法之一或将其保存在会话或模型中

我从there得到它

【讨论】:

以上是关于Spring Boot MVC 控制器是多线程的吗?的主要内容,如果未能解决你的问题,请参考以下文章

const是多线程安全的吗

debezium sqlServer 连接器任务是多线程的吗?

Spring Boot中Spring MVC的整合支持

asyncio.run_in_executor 是多线程的吗?

Disruptor - 消费者是多线程的吗?

Spring-boot - 如何同时使用 Resteasy JAX-RS 和 Spring MVC 控制器