【问题标题】:Spring race conditions singleton service stateful or statelessSpring 竞争条件单例服务有状态或无状态
【发布时间】:2015-09-25 09:58:02
【问题描述】:

我有点担心使用 Spring 的竞争条件。 我知道各种范围(单例、原型、会话等)之间的区别

我也知道:

  • 服务默认具有单例范围
  • 只要我的 bean 是无状态的,我就不会有竞争条件问题

尽管我对这个无国籍的事情不是 100% 确定的。在谈论状态时,我的研究来源总是只关注共享实例变量,但竞争条件不仅会在访问实例变量时出现。 我创建了以下示例来说明我的问题:

@Service
public class AppleService {

  @Autowired
  private AppleRepository appleRepository;

  @Override
  public void doSomethingWithAppleCategory(String appleCategory) {
    boolean existsAppleInCategory = existsAppleInCategory(appleCategory);
    if(existsAppleInCategory) {
        // do something
    }
    else {
        throw new RuntimeException("There is no apple in the category: " + appleCategory);
    }
  }

  private boolean existsAppleInCategory(String appleCategory) {
    Iterable<Apple> allApples = appleRepository.findAll();
    return allApples.stream().anyMatch(a -> a.getAppleCategory().equals(appleCategory));
  } 
}

您可以假设该服务用于休息控制器或类似的东西。据我了解,当调用方法 existsAppleInCategory 时,竞争条件可能会出现问题。例如,thread1 有它的插槽并将“false”写入变量 existsAppleInCategory。然后线程 2 用“true”覆盖它的插槽 existsAppleInCategory。之后线程 1 有另一个时隙。 => 线程 1 现在“做某事”而不是抛出 RuntimeException。

我的假设正确吗?在这种情况下,我是否遇到竞争条件问题? 如果不是为什么?您能否推荐任何有关该主题的详细资料(在线资料、书籍等)?

提前谢谢你!

【问题讨论】:

  • 没有。你的理解是错误的......没有共享状态(假设 AppleRepository 也是一个线程安全的单例)。每个线程都有自己的堆栈,并且在该堆栈上,除其他外,还有方法内部使用的变量,因此 thread1 无法从 thread2 看到 existsAppleInCategory,反之亦然。如果existsAppleInCategory 是实例级变量,这将有所不同!
  • 你当然是对的。现在我觉得我是个初学者:-)

标签: java multithreading spring spring-mvc race-condition


【解决方案1】:

假设 AppleRepository 是一个无状态的单例,并在其中省略了数据库(因为您的问题是关于 java 的)。

简而言之,thread1 看不到来自 thread2 的变量,因此您没有任何并发​​问题。

每个正在执行的线程都有自己的堆栈,在该堆栈上,除其他外,还有方法内部使用的变量,因此 thread1 无法从 thread2 看到 existsAppleInCategory,反之亦然。该变量是方法的本地变量(实际上是执行块),每个线程在堆栈上都有自己的副本。

如果existsAppleInCategory 是一个实例级别的变量,那么这将是不同的,因为您拥有共享状态。在这种情况下,线程可能会看到彼此的数据(取决于写入/读取状态的时间以及volatile 关键字的使用)。

【讨论】:

    【解决方案2】:

    正如 M.Deinum 在其评论中所说,您的代码不会出现任何竞争条件问题,因为 existsAppleInCategory 是函数的局部变量,不是实例变量。每个线程都有自己的副本,一切都会好起来的。

    这将是完全不同的

    @Service
    public class AppleService {
    
      @Autowired
      private AppleRepository appleRepository;
    
      private boolean _existsAppleInCategory;
    
      @Override
      public void productCallDetection(String appleCategory) {
        _existsAppleInCategory = existsAppleInCategory(appleCategory);
        if(existsAppleInCategory) {
            // do something
        }
        ...
      }
      ...
    }
    

    因为现在您正在更改将由所有线程共享的实例变量。

    【讨论】:

      【解决方案3】:

      在您的示例中,您可以认为 appleRepository 具有状态,因为正如您在示例中描述的那样,它可以更改。

      因此,您还必须考虑此类中的线程安全性(可能是序列化?)。显然,您的 AppleService 是线程安全的,因为它是无状态的单例。您的竞争条件问题将来自存储库,而不是单例。

      一个不错的基本阅读Java Tutorial Concurrency

      【讨论】:

        【解决方案4】:

        是的,这可能是数据库级别的竞争条件。

        如果您查询数据,然后在 相同的事务,常规的 SELECT 语句没有给出足够的 保护。其他事务可以更新或删除与您相同的行 刚刚查询。

        所以使用数据库锁定方法

        锁定读取(SELECT ... FOR UPDATE 和 SELECT ... LOCK IN SHARE 模式)

        如果您想在代码级别 https://docs.oracle.com/javase/tutorial/essential/concurrency/sync.html 中保护使用 synchronization 的 java 中的竞争条件

        更多:

        Mysql:http://dev.mysql.com/doc/refman/5.6/en/innodb-locking-reads.html

        PlSQL : http://www.techonthenet.com/oracle/cursors/for_update.php

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2020-02-14
          • 1970-01-01
          • 2020-11-06
          • 2014-07-04
          • 2020-02-29
          • 2011-02-05
          • 2015-09-18
          相关资源
          最近更新 更多