Pai*_*Red 7 java multithreading spring-mvc spring-boot
我是Spring MVC的新手,但是我在文档中什么都没有找到。假设我有一个控制器/ accounts,它接受POST请求来创建一个帐户。具有相同帐户ID的两个请求(几乎)同时出现。ASFAIK dispatcherServlet管理请求。
在第一个请求完成之前,是否将第二个请求放入队列?还是会有两个线程同时处理两个请求?
更新:
检查官方Spring教程:构建REST服务。有方法的控制器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();
}
Run Code Online (Sandbox Code Playgroud)
控制器只是bean,默认情况下它们是单例。当接收到两个同时请求时,两个线程将使用控制器的同一实例。假设第一个线程已经保存了新书签并正在执行
httpHeaders.setLocation(ServletUriComponentsBuilder
.fromCurrentRequest().path("/{id}")
.buildAndExpand(result.getId()).toUri());
Run Code Online (Sandbox Code Playgroud)
同时第二个线程刚刚执行
Bookmark result = bookmarkRepository.save(new Bookmark(account,
input.uri, input.description));
Run Code Online (Sandbox Code Playgroud)
在这种情况下,将返回result.getId()).toUri()由第二个线程创建的第一个线程。
它是正确的工作流程,并且该控制器是线程安全的吗?
对于该类型的所有框架,可以安全地假设控制器方法将同时处理(即在多个并发线程中)。此外,以任何其他方式执行此操作都会严重影响性能(是的,有些框架的设计与从头开始非常不同,但我们现在不讨论这些)。
Spring 本身无法知道 URL 的帐户 ID 部分在某种程度上是特殊的,无法在其上进行同步。如果它在 URL 级别上进行同步,则会导致性能下降。您始终可以自己实现此逻辑,例如,同步此控制器方法所操作的会话范围对象(或者按照Leo 的答案建议将整个控制器设置为会话范围)。然后,这将仅序列化该用户的调用(该 HTTP 会话)。如果您想在所有用户之间同步,您可以拥有全局共享锁,但由于上述原因,这是一个非常糟糕的主意。
相反,以所有控制器完全无状态的方式设计应用程序,并使方法尽可能具有幂等性或具有正确的前提条件检查逻辑。如果该方法正在写入数据库,您可以实现乐观锁定策略。Hibernate 已经具有该功能,并且某些 DB/驱动程序也具有该功能,因此请研究一下适用于您的情况的功能。
更新
在提供的示例中,两个线程都在保存一个实例(我假设保存意味着根据需要插入/更新),并且返回的对象是当前保存操作的结果,因此没有奇怪的线程间行为。这是假设保存是事务性的,具有合理的事务隔离级别(所有其他假设都是非常不寻常的)。事实上,第二个线程只会覆盖第一个线程写入的值,但这可能正是您所期望的。也就是说,您只读取下一行中的 id,并且可能永远不会改变,因此 URL 构建似乎不受并发的影响。