【问题标题】:Spring Service instance variable in a multi-threaded environment多线程环境中的 Spring Service 实例变量
【发布时间】:2018-10-15 20:50:24
【问题描述】:

我最近遇到了一个竞争条件问题,因为在默认范围(单例范围)服务类中声明了一个实例变量。实例变量的目的是使我的代码更具可读性,并避免将同一变量不断传递给 Service 类中的不同私有方法。示例如下:

@Service
public class SomeServiceImpl implements SomeService {
    private final StatusRepository statusRepository;
    private Predicate<Status> statusPredicate;

    @Autowired
    public SomeServiceImpl(StatusRepository statusRepository) {
        this.statusRepository = statusRepository;
    }

    @Override
    public List<Status> getAllowedStatuses(String userId) {
        statuses = statusRepository.getAll();
        initPredicate();
        appendPredicateA();
        appendPredicateB();
        List<Status> results = statuses.stream()
            .filter(statusPredicate)
            .collect(Collectors.toList());
        return results;
    }

    private void initPredicate() {
        statusPredicate = p -> p.getDefault().equals("default");
    }

    private void appendPredicateA() {
        statusPredicate.and(p -> p.getA().equals("A"));
    }

    private void appendPredicateB() {
        statusPredicate.and(p -> p.getB().equals("B"));
    }
}

这是我想要实现的目标的一个非常简单的示例。这显然不是线程安全的,因为现在服务类是有状态的。我可以通过将 statusPredicate 变量转换为局部变量并让 void 方法返回谓词来简单地解决此问题在它被附加了新的条件之后,它会变得像这样混乱:

@Override
public List<Status> getAllowedStatuses(String userId) {
    statuses = statusRepository.getAll();
    Predicate<Status> statusPredicate = p -> p.getDefault().equals("default");
    statusPredicate = appendPredicateA(statusPredicate);
    statusPredicate = appendPredicateB(statusPredicate);
    List<Status> results = statuses.stream()
        .filter(statusPredicate)
        .collect(Collectors.toList());
    return results;
}

它会不断调用修改变量并返回变量。

我知道一些可以解决此问题的解决方案,例如在 Service 类中添加 @RequestScope 以确保来自 HTTP 的每个请求都会获得一个新的 服务对象,或在 Predicate 变量上使用 ThreadLocal。但是,我不太确定什么是最好的方法,以及在 Service 类中声明实例变量是否可以开始。如果从一开始就使 Service 类有状态是不好的,我应该如何构造我的代码以使其更清洁并仍然保持无状态?

请指教!在此先感谢:D

【问题讨论】:

  • 出于您怀疑的原因,将服务类设为有状态实际上是不好的。通常最好选择您的其他选择,这确实不是那么糟糕。如果您开始获得大量自定义谓词,则始终可以将它们放在一个列表中并遍历该列表。
  • 原代码还不错,可以写的更简洁一些。正在考虑的选项都更糟。
  • @chrylis 感谢您的建议! :D
  • @NathanHughes 您能否详细说明为什么正在考虑的选项都更糟?是不是因为它们违反了单例的契约以及使用 ThreadLocal 产生的潜在不必要的复杂性?另外,你能提供一个关于如何更简洁地编写示例代码的小例子吗?提前致谢!

标签: java spring multithreading spring-mvc thread-safety


【解决方案1】:

Spring Service 可以是有状态的,这就是作用域的原因。

Spring Service 的默认范围是 Singleton,因为这是最广泛的用例。但是你不应该使用可变的局部变量。

简单地说,尝试无状态设计并仅在类实例化速度快的情况下使用作用域来解决问题,否则 TreadLocal 会表现得更好。

【讨论】:

  • 感谢您的回复!在示例中,我想将三个过滤器分成三个私有方法,并在满足条件时应用每个过滤器(基本的 if 语句)。如果我使用 Singleton 范围而不使用 ThreadLocal 作为类变量,我需要将变量设为本地。在这种情况下,它必须是一个可变的局部变量,因为每个过滤器可能会或可能不会被应用。由于我不想在每次应用过滤器时重新分配局部变量,我该如何着手实现这一目标。有什么更好的解决方案可以让这段代码更优雅、更有效?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-03-05
  • 1970-01-01
相关资源
最近更新 更多