Spring 竞争条件单例服务有状态或无状态

Spring race conditions singleton service stateful or stateless

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

我也知道:

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

@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 "does something" 而不是抛出 RuntimeException。

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

提前致谢!

在你的例子中,你可以认为 appleRepository 有状态,因为正如你在你的例子中描述的那样,它可以改变。

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

一本不错的基础读物Java Tutorial Concurrency

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

If you query data and then insert or update related data within the same transaction, the regular SELECT statement does not give enough protection. Other transactions can update or delete the same rows you just queried.

因此使用

使用数据库锁定方法

Locking Reads (SELECT ... FOR UPDATE and SELECT ... LOCK IN SHARE MODE)

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

中使用 synchronization

更多:

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

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

正如 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
    }
    ...
  }
  ...
}

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

假设 AppleRepository 是一个无状态的单例并且在其中省略了数据库(正如你的问题关于 java)。

简而言之,线程 1 看不到线程 2 中的变量,因此您没有任何并发​​问题。

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

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