【问题标题】:Spring @Service thread safety concern on @PutMapping concurrent requests关于@PutMapping 并发请求的Spring @Service 线程安全问题
【发布时间】:2019-01-25 11:05:11
【问题描述】:

我们有一个如下所示的控制器:

@RestController()
public class TestController {

  @Autowired
  TestService testService;

  ....

  // Update request
  @PutMapping("/update")
  public ResponseEntity<Sample> updateApi(@PathVariable(value = "id") Long id,
      @Valid @RequestBody Sample sampleDetails) {
    return new ResponseEntity<Sample>(testService.updateSample(id, sampleDetails), HttpStatus.OK);
  }

....

服务类(依赖于 JPA 存储库)如下所示:

@Service
public class TestService {

  @Autowired
  TestRepository testRepository;

  // update sample
  public Api updateSample(long sampleId, Sample details) {

    // Get object to be updated
    Sample sample = apiRepository.findById(sampleId);

    // Update required fields
    sample.setName(details.getName());
    sample.setBody(details.getBody());
    return apiRepository.save(api);
  }
  1. 在上述服务(TestService)中,setName 和 setBody 方法调用 jpa 存储库:这些步骤是否保持状态(即非无状态),并且在处理并发请求时可能会导致问题。或者我们是否需要将此服务(或服务中的 updateSample 方法)的范围设置为“web-aware Spring ApplicationContext”
  2. 与第 1 点类似,此行:“Sample sample = apiRepository.findById(sampleId);”,此行是线程安全的还是会在处理并发请求时导致问题。

【问题讨论】:

  • 需要考虑两件事:1) 数据库隔离级别,锁定 2) @Transactional - 如果您的方法上没有该注释,那么这些调用将在不同的事务中。
  • 这段代码是完全线程安全的。只要你不在类级别保持状态就没有问题,
  • 不要在类级别保持状态 => 这是否意味着在方法内部创建的变量是线程安全的?并且只有单个线程将在给定时间点执行该方法?
  • 你的 bean 是原型还是单例?我认为该方法不是“线程安全的”。线程 A 和线程 B 修改同一个 sampleId 对象,数据库中的结果可能不是你想要的。
  • 它只是一个单例

标签: java spring spring-boot


【解决方案1】:

您的代码中没有任何线程在线程之间共享的状态。 除非您的存储库不返回一些可以在几个线程之间共享的缓存对象。

如果存储库对每个 findById 方法调用进行 DB 调用并创建新的 Sample 对象,则该对象仅对当前线程可见。 Why are local variables thread safe in Java

但是你有Transaction Isolation的问题,因为服务的方法updateSample在事务范围之外执行并且DB中的数据可以被findByIdsave之间的另一个线程或进程更改。

【讨论】:

  • 解决“updateSample”中的线程问题的最佳方法是什么:我们应该将范围设置为请求感知还是只同步语句集?
  • @Neo 可以用@Transaction注解标记方法,使用乐观/悲观锁解决并发修改问题baeldung.com/jpa-optimistic-locking
【解决方案2】:

好吧,让我们深入了解如何处理您的示例:

  1. 通常应用服务器(tomcat, undertown, jetty, others)管理一个线程池,每个请求都由一个安全的线程处理。
  2. 当用户请求进入Controller 时,会创建一个线程安全的并保持自身,直到控制器返回响应以完成请求。所以请求在Service 中执行一些逻辑并且调用Repository 将在线程安全中。
  3. 您可以使用一些压力工具(Gatling, JMeter, or other) 使用并发请求测试这个简单的代码,并且您不会有任何问题,因为应用程序服务器创建了一个线程池来管理并发处理的线程保险箱,所以所有他们使用的共享资源(服务、存储库等)必须确保线程安全。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2021-02-21
    • 1970-01-01
    • 2016-01-15
    • 1970-01-01
    • 1970-01-01
    • 2015-05-09
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多